1220 lines
51 KiB
Java
1220 lines
51 KiB
Java
|
/**
|
||
|
* MemCached Java client
|
||
|
* Copyright (c) 2003
|
||
|
* Richard 'toast' Russo <russor@msoe.edu>
|
||
|
* http://people.msoe.edu/~russor/memcached
|
||
|
*
|
||
|
* Originally translated from Brad Fitzpatrick's <brad@danga.com> MemCached Perl client
|
||
|
* See the memcached website:
|
||
|
* http://www.danga.com/memcached/
|
||
|
*
|
||
|
* This module is Copyright (c) 2003 Richard Russo.
|
||
|
* All rights reserved.
|
||
|
* You may distribute under the terms of the GNU General Public License
|
||
|
* This is free software. IT COMES WITHOUT WARRANTY OF ANY KIND.
|
||
|
*
|
||
|
* @author Richard 'toast' Russo <russor@msoe.edu>
|
||
|
* @version 0.9.1
|
||
|
*/
|
||
|
|
||
|
|
||
|
package com.danga.MemCached;
|
||
|
|
||
|
|
||
|
import java.util.zip.*;
|
||
|
import java.util.*;
|
||
|
import java.util.Map.*;
|
||
|
import java.io.*;
|
||
|
|
||
|
|
||
|
|
||
|
/** This is a Java client for the memcached server available from
|
||
|
* <a href="http:/www.danga.com/memcached/">http://www.danga.com/memcached/</a>.*/
|
||
|
public class MemCachedClient {
|
||
|
|
||
|
final int F_COMPRESSED = 2;
|
||
|
final int F_SERIALIZED = 8;
|
||
|
//using 8 (1 << 3) so other clients don't try to unpickle/unstore/whatever
|
||
|
//things that are serialized... I don't think they'd like it. :)
|
||
|
|
||
|
ArrayList buckets;
|
||
|
HashMap host_dead;
|
||
|
HashMap stats;
|
||
|
HashMap sockets;
|
||
|
boolean debug = false;
|
||
|
boolean forceserial = false;
|
||
|
|
||
|
String singlesock = null;
|
||
|
double compress_savings = 0.20;
|
||
|
boolean compress_enable = true;
|
||
|
int compress_threshold = 1024; // FIXME: is this a reasonable default??
|
||
|
|
||
|
/** Creates a new instance of MemCachedClient.
|
||
|
*
|
||
|
* By default, compression is enabled, with a threshold of 1024, and required
|
||
|
* savings of 0.2; debug is disabled; forced serialization is disabled; and the
|
||
|
* server list is empty.
|
||
|
*/
|
||
|
public MemCachedClient() {
|
||
|
}
|
||
|
|
||
|
|
||
|
/** Sets the required change in size for value storage to use compression.
|
||
|
* This rate is expressed as a decimal, for example 0.20 means that unless the data
|
||
|
* becomes 20% smaller (it will then be at 80% of its original size), it will be
|
||
|
* stored in uncompressed form.
|
||
|
*
|
||
|
* The value defaults to 0.20.
|
||
|
* @param d required compression to store compressed data
|
||
|
*/
|
||
|
public void set_compress_savings(double d) {
|
||
|
compress_savings = d;
|
||
|
}
|
||
|
|
||
|
/** Enable storing compressed data, provided it meets the threshold and savings
|
||
|
* requirements. If enabled, data will be stored in compressed form if it is
|
||
|
* longer than the threshold length set with {@link #set_compress_threshold(int) set_compress_threshold()}
|
||
|
* and compression results in a space savings of at least the rate set with
|
||
|
* {@link #set_compress_savings(double) set_compress_savings()}.
|
||
|
*
|
||
|
* The default is that compression is enabled.
|
||
|
*
|
||
|
* Even if compression is disabled, compressed data will be automatically
|
||
|
* decompressed.
|
||
|
* @param b <CODE>true</CODE> to enable compression, <CODE>false</CODE> to disable compression
|
||
|
*/
|
||
|
public void set_compress_enable(boolean b) {
|
||
|
compress_enable = b;
|
||
|
}
|
||
|
|
||
|
/** Sets the required length for data to be considered for compression. If the
|
||
|
* length of the data to be stored is not equal or larger than this value, it will
|
||
|
* not be compressed.
|
||
|
*
|
||
|
* This defaults to 1024.
|
||
|
* @param i required length of data to consider compression
|
||
|
*/
|
||
|
public void set_compress_threshold(int i) {
|
||
|
compress_threshold = i;
|
||
|
}
|
||
|
|
||
|
/** This function lets you tell the client to always serialize data. This is mostly
|
||
|
* useful for debugging; or if you don't want to store stringified numbers.
|
||
|
* @param b True if you want to always serialize data, false if you want to let the client
|
||
|
* decide.
|
||
|
*
|
||
|
*/
|
||
|
public void set_serial(boolean b) {
|
||
|
forceserial = b;
|
||
|
}
|
||
|
|
||
|
/** Turns on or off debugging information. If enabled, the client will print
|
||
|
* status messages that may be helpful in tracking the progress of data through the
|
||
|
* system. If you're not having problems, this is probably not useful for you.
|
||
|
* @param b <CODE>true</CODE>, if debugging messages are desired, <CODE>false</CODE> if debugging messages are not
|
||
|
* desired
|
||
|
*/
|
||
|
public void set_debug(boolean b) {
|
||
|
debug = b;
|
||
|
}
|
||
|
|
||
|
/** This lets you determine if the client will serialize everything, or only data
|
||
|
* types that it doesn't know to stringify
|
||
|
* @return true, if the client is serializing everything
|
||
|
*/
|
||
|
public boolean get_serial() {
|
||
|
return forceserial;
|
||
|
}
|
||
|
|
||
|
/** Set the list of servers to use. All servers have an equal weight (1)
|
||
|
* @param serverlist An array of servers to use; servers should be in the form ip:port. Hostnames
|
||
|
* are acceptable.
|
||
|
*/
|
||
|
public void set_servers(String[] serverlist) {
|
||
|
set_servers(serverlist, null);
|
||
|
}
|
||
|
|
||
|
/** Sets the list of servers, and their weights; For best results keep the weights
|
||
|
* as low as possible. If the weightlist is shorter than the serverlist, remaining
|
||
|
* servers are defaulted to a weight of 1.
|
||
|
* @param serverlist A list of servers, in the form ip:port. Hostnames are also acceptable
|
||
|
* @param weightlist A list of weights for the servers. Negative or zero values will be ignored, and
|
||
|
* the default weight of 1 will be used instead
|
||
|
*
|
||
|
*/
|
||
|
public void set_servers(String[] serverlist, int[] weightlist) {
|
||
|
/* I'm just going to construct the bucket list here, since this is the only place
|
||
|
* that the server list is going to change. The perl client constructs it elsewhere. */
|
||
|
buckets = new ArrayList();
|
||
|
host_dead = new HashMap();
|
||
|
sockets = new HashMap();
|
||
|
if (serverlist.length == 1) {
|
||
|
singlesock = serverlist[0];
|
||
|
} else {
|
||
|
singlesock = null;
|
||
|
}
|
||
|
|
||
|
|
||
|
for (int i = 0; i < serverlist.length; ++i) {
|
||
|
if (weightlist != null && weightlist.length >i) {
|
||
|
for (int j = 0; j < weightlist[i]; ++j) {
|
||
|
buckets.add(serverlist[i]);
|
||
|
}
|
||
|
} else {
|
||
|
buckets.add(serverlist[i]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private SockIO get_sock(Object key) {
|
||
|
if (singlesock != null) {
|
||
|
return sock_to_host(singlesock);
|
||
|
}
|
||
|
if (key.getClass() == Integer.class) {
|
||
|
return get_sock(((Integer)key).intValue());
|
||
|
} else {
|
||
|
return get_sock(key.toString());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
private SockIO get_sock(String key) {
|
||
|
if (singlesock != null) {
|
||
|
return sock_to_host(singlesock);
|
||
|
}
|
||
|
return get_sock(hashfunc(key));
|
||
|
}
|
||
|
|
||
|
private SockIO get_sock(int key) {
|
||
|
if (singlesock != null) {
|
||
|
return sock_to_host(singlesock);
|
||
|
}
|
||
|
if ( buckets.size() == 0) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
int tries = 0;
|
||
|
int hv = key;
|
||
|
while (tries++ < 20) {
|
||
|
String host = (String) buckets.get(hv % buckets.size());
|
||
|
SockIO sock = sock_to_host(host);
|
||
|
if (sock != null) {
|
||
|
return sock;
|
||
|
}
|
||
|
hv += hashfunc("" + tries + key); // stupid, but works
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
private SockIO sock_to_host(String host) {
|
||
|
Date now = new Date();
|
||
|
int tmp = host.indexOf(":");
|
||
|
String[] ip ={ host.substring(0,tmp), host.substring(tmp+1)};
|
||
|
|
||
|
if ((host_dead.containsKey(host) && now.before((Date)host_dead.get(host))) ||
|
||
|
(host_dead.containsKey(ip[0]) && now.before((Date)host_dead.get(ip[0])))) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
if (sockets.containsKey(host) && ((SockIO)sockets.get(host)).isConnected()) {
|
||
|
return (SockIO) sockets.get(host);
|
||
|
}
|
||
|
|
||
|
SockIO sock;
|
||
|
try {
|
||
|
sock = new SockIO(ip[0], Integer.decode(ip[1]).intValue());
|
||
|
|
||
|
} catch (Exception e) {
|
||
|
sock = null;
|
||
|
}
|
||
|
if (sock != null) {
|
||
|
sockets.put(host, sock);
|
||
|
host_dead.remove(host);
|
||
|
host_dead.remove(ip);
|
||
|
return sock;
|
||
|
}
|
||
|
now = new Date();
|
||
|
host_dead.put(host, new Date(now.getTime() + 60000 + (int)(java.lang.Math.random() * 10000)));
|
||
|
host_dead.put(ip[0], new Date(now.getTime() + 60000 + (int)(java.lang.Math.random() * 10000)));
|
||
|
if (debug) {
|
||
|
System.out.println("MemCachedClient: marking " + host + " (" + ip[0] + ") dead\n");
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
private int hashfunc(String key) {
|
||
|
int hash = 0;
|
||
|
for (int i = 0; i < key.length(); ++i) {
|
||
|
hash = hash*33 + key.charAt(i);
|
||
|
}
|
||
|
return hash;
|
||
|
}
|
||
|
|
||
|
/** Forget that servers were unreachable. This is useful if a network connection has
|
||
|
* been restored, and servers that were recently unreachable have become reachable
|
||
|
* again.
|
||
|
*/
|
||
|
public void forget_dead_hosts() {
|
||
|
host_dead.clear();
|
||
|
}
|
||
|
|
||
|
/** Disconnect from all memcached servers. */
|
||
|
public void disconnect_all() {
|
||
|
Iterator i = sockets.values().iterator();
|
||
|
while (i.hasNext()) {
|
||
|
((SockIO)i.next()).close();
|
||
|
i.remove();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* wouldn't it be nice if java had default parameters like c? */
|
||
|
|
||
|
/** Deletes a key from the server; only the key is specified.
|
||
|
* @param key the key to be removed
|
||
|
* @return true, if the data was deleted successfully
|
||
|
*/
|
||
|
public boolean delete(String key) {
|
||
|
return delete(key,null, null);
|
||
|
}
|
||
|
/** Deletes a key from the server; the key, and a hash value are specified.
|
||
|
* @return true, if the data was deleted successfully
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @param key the key to be removed
|
||
|
*/
|
||
|
public boolean delete(String key, int hash) {
|
||
|
return delete(key,null,new Integer(hash));
|
||
|
}
|
||
|
/** Deletes a key from the server; the key, and a hash value are specified.
|
||
|
* @return true, if the data was deleted successfully
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @param key the key to be removed
|
||
|
*/
|
||
|
public boolean delete(String key, Integer hash) {
|
||
|
return delete(key, null, hash);
|
||
|
}
|
||
|
/** Deletes a key from the server; the key, and a delete time are specified. The item
|
||
|
* is immediately made non retrievable, however {@link #add(String, Object) add} and
|
||
|
* {@link #replace(String, Object) replace} will fail when used with the same key will
|
||
|
* fail, until the server reaches the specified time. However,
|
||
|
* {@link #set(String, Object) set} will succeed, and the new value will not
|
||
|
* be deleted.
|
||
|
*
|
||
|
* @return true, if the data was deleted successfully
|
||
|
* @param expiry when to expire the record
|
||
|
* @param key the key to be removed
|
||
|
*/
|
||
|
|
||
|
public boolean delete(String key, Date expiry) {
|
||
|
return delete(key,expiry,null);
|
||
|
}
|
||
|
/** Deletes a key from the server; the key, a delete time are specified, and a hash value
|
||
|
* are specified. The item is immediately made non retrievable, however
|
||
|
* {@link #add(String, Object) add} and {@link #replace(String, Object) replace}
|
||
|
* will fail when used with the same key will fail, until the server reaches the
|
||
|
* specified time. However, {@link #set(String, Object) set} will succeed,
|
||
|
* and the new value will not be deleted.
|
||
|
*
|
||
|
* @return true, if the data was deleted successfully
|
||
|
* @param expiry when to expire the record.
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @param key the key to be removed
|
||
|
*/
|
||
|
public boolean delete(String key, Date expiry, int hash) {
|
||
|
return delete(key, expiry, new Integer(hash));
|
||
|
}
|
||
|
|
||
|
/** Deletes a key from the server; the key, a delete time are specified, and a hash value
|
||
|
* are specified. The item is immediately made non retrievable, however
|
||
|
* {@link #add(String, Object) add} and {@link #replace(String, Object) replace}
|
||
|
* will fail when used with the same key will fail, until the server reaches the
|
||
|
* specified time. However, {@link #set(String, Object) set} will succeed,
|
||
|
* and the new value will not be deleted.
|
||
|
*
|
||
|
* @return true, if the data was deleted successfully
|
||
|
* @param expiry when to expire the record.
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @param key the key to be removed
|
||
|
*/
|
||
|
public boolean delete(String key, Date expiry, Object hash) {
|
||
|
SockIO sock;
|
||
|
if (hash != null) {
|
||
|
sock = get_sock(hash);
|
||
|
} else {
|
||
|
sock = get_sock(key);
|
||
|
}
|
||
|
|
||
|
if (sock == null) {
|
||
|
return false;
|
||
|
}
|
||
|
String command = "delete " + key;
|
||
|
if (expiry != null) {
|
||
|
command = command + " " + expiry.getTime() / 1000;
|
||
|
}
|
||
|
command = command + "\r\n";
|
||
|
|
||
|
try {
|
||
|
sock.writeBytes(command);
|
||
|
sock.flush();
|
||
|
|
||
|
command = sock.readLine();
|
||
|
if (command.equals("DELETED")) {
|
||
|
return true;
|
||
|
}
|
||
|
} catch (IOException e) {
|
||
|
sock.close();
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/* Blah.... Default parameters would be _REALLY_ nice here */
|
||
|
|
||
|
/** Stores data on the server; only the key and the value are specified.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean set(String key, Object value) {
|
||
|
return set("set", key, value, null, null);
|
||
|
}
|
||
|
/** Stores data on the server; the key, value, and a hash value are specified.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean set(String key, Object value, int hash) {
|
||
|
return set("set",key,value,null,new Integer(hash));
|
||
|
}
|
||
|
/** Stores data on the server; the key, value, and a hash value are specified.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean set(String key, Object value, Integer hash) {
|
||
|
return set("set",key,value, null, hash);
|
||
|
}
|
||
|
|
||
|
/** Stores data on the server; the key, value, and an expiration time are specified.
|
||
|
* The server will automatically delete the value when the expiration time has been reached.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @param expiry when to expire the record
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean set(String key, Object value, Date expiry) {
|
||
|
return set("set",key,value,expiry,null);
|
||
|
}
|
||
|
/** Stores data on the server; the key, value, an expiration time, and a hash value are specified.
|
||
|
* The server will automatically delete the value when the expiration time has been reached.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @param expiry when to expire the record
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean set(String key, Object value, Date expiry, int hash) {
|
||
|
return set("set",key,value, expiry, new Integer(hash));
|
||
|
}
|
||
|
/** Stores data on the server; the key, value, an expiration time, and a hash value are specified.
|
||
|
* The server will automatically delete the value when the expiration time has been reached.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @param expiry when to expire the record
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean set(String key, Object value, Date expiry, Object hash) {
|
||
|
return set("set",key,value, expiry, hash);
|
||
|
}
|
||
|
|
||
|
|
||
|
/** Adds data to the server; only the key and the value are specified.
|
||
|
* If data already exists for this key on the server or if the key is being
|
||
|
* deleted, the specified value will not be stored.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean add(String key, Object value) {
|
||
|
return set("add", key, value, null, null);
|
||
|
}
|
||
|
/** Adds data to the server; the key, value, and a hash value are specified.
|
||
|
* If data already exists for this key on the server or if the key is being
|
||
|
* deleted, the specified value will not be stored.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean add(String key, Object value, int hash) {
|
||
|
return set("add",key,value,null,new Integer(hash));
|
||
|
}
|
||
|
/** Adds data to the server; the key, value, and a hash value are specified.
|
||
|
* If data already exists for this key on the server or if the key is being
|
||
|
* deleted, the specified value will not be stored.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean add(String key, Object value, Integer hash) {
|
||
|
return set("add",key,value, null, hash);
|
||
|
}
|
||
|
|
||
|
/** Adds data to the server; the key, value, and an expiration time are specified.
|
||
|
* If data already exists for this key on the server or if the key is being
|
||
|
* deleted, the specified value will not be stored.
|
||
|
* The server will automatically delete the value when the expiration time has been reached.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @param expiry when to expire the record
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean add(String key, Object value, Date expiry) {
|
||
|
return set("add",key,value,expiry,null);
|
||
|
}
|
||
|
/** Adds data to the server; the key, value, an expiration time, and a hash value are specified.
|
||
|
* If data already exists for this key on the server or if the key is being
|
||
|
* deleted, the specified value will not be stored.
|
||
|
* The server will automatically delete the value when the expiration time has been reached.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @param expiry when to expire the record
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean add(String key, Object value, Date expiry, int hash) {
|
||
|
return set("add",key,value, expiry, new Integer(hash));
|
||
|
}
|
||
|
/** Adds data to the server; the key, value, an expiration time, and a hash value are specified.
|
||
|
* If data already exists for this key on the server or if the key is being
|
||
|
* deleted, the specified value will not be stored.
|
||
|
* The server will automatically delete the value when the expiration time has been reached.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @param expiry when to expire the record
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean add(String key, Object value, Date expiry, Object hash) {
|
||
|
return set("add",key,value, expiry, hash);
|
||
|
}
|
||
|
|
||
|
/** Updates data on the server; only the key and the value are specified.
|
||
|
* If data does not already exist for this key on the server, or if the key is being
|
||
|
* deleted, the specified value will not be stored.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean replace(String key, Object value) {
|
||
|
return set("replace", key, value, null, null);
|
||
|
}
|
||
|
/** Updates data on the server; the key, value, and a hash value are specified.
|
||
|
* If data does not already exist for this key on the server, or if the key is being
|
||
|
* deleted, the specified value will not be stored.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean replace(String key, Object value, int hash) {
|
||
|
return set("replace",key,value,null,new Integer(hash));
|
||
|
}
|
||
|
/** Updates data on the server; the key, value, and a hash value are specified.
|
||
|
* If data does not already exist for this key on the server, or if the key is being
|
||
|
* deleted, the specified value will not be stored.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean replace(String key, Object value, Integer hash) {
|
||
|
return set("replace",key,value, null, hash);
|
||
|
}
|
||
|
|
||
|
/** Updates data on the server; the key, value, and an expiration time are specified.
|
||
|
* If data does not already exist for this key on the server, or if the key is being
|
||
|
* deleted, the specified value will not be stored.
|
||
|
* The server will automatically delete the value when the expiration time has been reached.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @param expiry when to expire the record
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean replace(String key, Object value, Date expiry) {
|
||
|
return set("replace",key,value,expiry,null);
|
||
|
}
|
||
|
/** Updates data on the server; the key, value, an expiration time, and a hash value are specified.
|
||
|
* If data does not already exist for this key on the server, or if the key is being
|
||
|
* deleted, the specified value will not be stored.
|
||
|
* The server will automatically delete the value when the expiration time has been reached.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @param expiry when to expire the record
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean replace(String key, Object value, Date expiry, int hash) {
|
||
|
return set("replace",key,value, expiry, new Integer(hash));
|
||
|
}
|
||
|
/** Updates data on the server; the key, value, an expiration time, and a hash value are specified.
|
||
|
* If data does not already exist for this key on the server, or if the key is being
|
||
|
* deleted, the specified value will not be stored.
|
||
|
* The server will automatically delete the value when the expiration time has been reached.
|
||
|
* If the value is not a String or numeric type, or if {@link #set_serial(boolean) set_serial(true)}
|
||
|
* has been called; the data will be Serialized prior to storage.
|
||
|
*
|
||
|
* If compression is enabled, and the data is longer than the compression threshold,
|
||
|
* and compresses by at least the compression savings, the data will be stored in
|
||
|
* compressed form.
|
||
|
*
|
||
|
* @param key key to store data under
|
||
|
* @param value value to store
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @param expiry when to expire the record
|
||
|
* @return true, if the data was successfully stored
|
||
|
*/
|
||
|
public boolean replace(String key, Object value, Date expiry, Object hash) {
|
||
|
return set("replace",key,value, expiry, hash);
|
||
|
}
|
||
|
|
||
|
|
||
|
private boolean set(String cmdname, String key, Object value, Date expiry, Object hash) {
|
||
|
SockIO sock;
|
||
|
if (hash != null) {
|
||
|
sock = get_sock(hash);
|
||
|
} else {
|
||
|
sock = get_sock(key);
|
||
|
}
|
||
|
|
||
|
if (sock == null) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (expiry == null) {
|
||
|
expiry = new Date(0);
|
||
|
}
|
||
|
int flags = 0;
|
||
|
|
||
|
byte[] val;
|
||
|
|
||
|
if (!forceserial && (value.getClass() == String.class ||
|
||
|
value.getClass() == Double.class ||
|
||
|
value.getClass() == Float.class ||
|
||
|
value.getClass() == Integer.class ||
|
||
|
value.getClass() == Long.class ||
|
||
|
value.getClass() == Byte.class ||
|
||
|
value.getClass() == Short.class)) {
|
||
|
val = value.toString().getBytes();
|
||
|
} else {
|
||
|
if (debug) {
|
||
|
System.out.println("MemCache: Serializing " + value.getClass().getName());
|
||
|
}
|
||
|
flags|= F_SERIALIZED;
|
||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||
|
try {
|
||
|
(new ObjectOutputStream(bos)).writeObject(value);
|
||
|
val = bos.toByteArray();
|
||
|
} catch (IOException e) {
|
||
|
val = value.toString().getBytes();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
if (compress_enable && val.length > compress_threshold) {
|
||
|
try {
|
||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream(val.length);
|
||
|
GZIPOutputStream gos = new GZIPOutputStream(bos);
|
||
|
gos.write(val);
|
||
|
gos.finish();
|
||
|
|
||
|
|
||
|
if (bos.size() <= ( int)((1 - compress_savings) *(double) val.length)) {
|
||
|
val = bos.toByteArray();
|
||
|
flags|= F_COMPRESSED;
|
||
|
}
|
||
|
|
||
|
} catch (IOException e) {
|
||
|
System.out.println(e.getMessage());
|
||
|
}
|
||
|
}
|
||
|
try {
|
||
|
String cmd = cmdname + " " + key + " " + flags + " " +
|
||
|
expiry.getTime() / 1000 + " " + val.length + "\r\n";
|
||
|
sock.writeBytes(cmd);
|
||
|
sock.write(val);
|
||
|
sock.writeBytes("\r\n");
|
||
|
sock.flush();
|
||
|
|
||
|
String line = sock.readLine();
|
||
|
if (debug) {
|
||
|
System.out.println("MemCache: " + cmdname + " " + key +
|
||
|
" = " + val + "(" + line + ")");
|
||
|
}
|
||
|
if (line.equals("STORED")) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
} catch (IOException e) {
|
||
|
sock.close();
|
||
|
}
|
||
|
return false;
|
||
|
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/** Increment the value at the specified key by 1, and then return it.
|
||
|
*
|
||
|
* Note that the server uses a 32-bit unsigned integer, and does not check
|
||
|
* for overflow. Because Java lacks unsigned types, the value is returned as
|
||
|
* a 64-bit integer. The server will only increment a value if it already exists;
|
||
|
* if a value is not found, -1 will be returned.
|
||
|
*
|
||
|
* @param key key where the data is stored
|
||
|
* @return -1, if the key is not found, the value after incrementing otherwise
|
||
|
*/
|
||
|
public long incr(String key) {
|
||
|
return incrdecr("incr", key, 1, null);
|
||
|
}
|
||
|
/** Increment the value at the specified key by the specified increment,
|
||
|
* and then return it.
|
||
|
*
|
||
|
* Note that the server uses a 32-bit unsigned integer, and does not check
|
||
|
* for overflow. Because Java lacks unsigned types, the value is returned as
|
||
|
* a 64-bit integer. The server will only increment a value if it already exists;
|
||
|
* if a value is not found, -1 will be returned.
|
||
|
*
|
||
|
* @param key key where the data is stored
|
||
|
* @param inc how much to increment by
|
||
|
* @return -1, if the key is not found, the value after incrementing otherwise
|
||
|
*/
|
||
|
public long incr(String key, long inc) {
|
||
|
return incrdecr("incr", key, inc, null);
|
||
|
}
|
||
|
/** Increment the value at the specified key by 1, and then return it.
|
||
|
*
|
||
|
* Note that the server uses a 32-bit unsigned integer, and does not check
|
||
|
* for overflow. Because Java lacks unsigned types, the value is returned as
|
||
|
* a 64-bit integer. The server will only increment a value if it already exists;
|
||
|
* if a value is not found, -1 will be returned.
|
||
|
*
|
||
|
* @param key key where the data is stored
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @return -1, if the key is not found, the value after incrementing otherwise
|
||
|
*/
|
||
|
public long incr(String key, Object hash) {
|
||
|
return incrdecr("incr", key, 1, hash);
|
||
|
}
|
||
|
/** Increment the value at the specified key by 1, and then return it.
|
||
|
*
|
||
|
* Note that the server uses a 32-bit unsigned integer, and does not check
|
||
|
* for overflow. Because Java lacks unsigned types, the value is returned as
|
||
|
* a 64-bit integer. The server will only increment a value if it already exists;
|
||
|
* if a value is not found, -1 will be returned.
|
||
|
*
|
||
|
* @param key key where the data is stored
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @return -1, if the key is not found, the value after incrementing otherwise
|
||
|
*/
|
||
|
public long incr(String key, int hash) {
|
||
|
return incrdecr("incr", key, 1, new Integer(hash));
|
||
|
}
|
||
|
/** Increment the value at the specified key by the specified increment,
|
||
|
* and then return it.
|
||
|
*
|
||
|
* Note that the server uses a 32-bit unsigned integer, and does not check
|
||
|
* for overflow. Because Java lacks unsigned types, the value is returned as
|
||
|
* a 64-bit integer. The server will only increment a value if it already exists;
|
||
|
* if a value is not found, -1 will be returned.
|
||
|
*
|
||
|
* @param key key where the data is stored
|
||
|
* @param inc how much to increment by
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @return -1, if the key is not found, the value after incrementing otherwise
|
||
|
*/
|
||
|
public long incr(String key, long inc, int hash) {
|
||
|
return incrdecr("incr", key, inc, new Integer(hash));
|
||
|
}
|
||
|
/** Increment the value at the specified key by the specified increment,
|
||
|
* and then return it.
|
||
|
*
|
||
|
* Note that the server uses a 32-bit unsigned integer, and does not check
|
||
|
* for overflow. Because Java lacks unsigned types, the value is returned as
|
||
|
* a 64-bit integer. The server will only increment a value if it already exists;
|
||
|
* if a value is not found, -1 will be returned.
|
||
|
*
|
||
|
* @param key key where the data is stored
|
||
|
* @param inc how much to increment by
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @return -1, if the key is not found, the value after incrementing otherwise
|
||
|
*/
|
||
|
public long incr(String key, long inc, Object hash) {
|
||
|
return incrdecr("incr", key, inc, hash);
|
||
|
}
|
||
|
|
||
|
/** Decrement the value at the specified key by 1, and then return it.
|
||
|
*
|
||
|
* Note that the server uses a 32-bit unsigned integer, and checks for
|
||
|
* underflow. In the event of underflow, the result will be zero. Because
|
||
|
* Java lacks unsigned types, the value is returned as a 64-bit integer.
|
||
|
* The server will only decrement a value if it already exists;
|
||
|
* if a value is not found, -1 will be returned.
|
||
|
*
|
||
|
* @param key key where the data is stored
|
||
|
* @return -1, if the key is not found, the value after incrementing otherwise
|
||
|
*/
|
||
|
public long decr(String key) {
|
||
|
return incrdecr("decr", key, 1, null);
|
||
|
}
|
||
|
/** Decrement the value at the specified key by the specified increment,
|
||
|
* and then return it.
|
||
|
*
|
||
|
* Note that the server uses a 32-bit unsigned integer, and checks for
|
||
|
* underflow. In the event of underflow, the result will be zero. Because
|
||
|
* Java lacks unsigned types, the value is returned as a 64-bit integer.
|
||
|
* The server will only decrement a value if it already exists;
|
||
|
* if a value is not found, -1 will be returned.
|
||
|
*
|
||
|
* @param key key where the data is stored
|
||
|
* @param inc how much to increment by
|
||
|
* @return -1, if the key is not found, the value after incrementing otherwise
|
||
|
*/
|
||
|
public long decr(String key, long inc) {
|
||
|
return incrdecr("decr", key, inc, null);
|
||
|
}
|
||
|
/** Decrement the value at the specified key by 1, and then return it.
|
||
|
*
|
||
|
* Note that the server uses a 32-bit unsigned integer, and checks for
|
||
|
* underflow. In the event of underflow, the result will be zero. Because
|
||
|
* Java lacks unsigned types, the value is returned as a 64-bit integer.
|
||
|
* The server will only decrement a value if it already exists;
|
||
|
* if a value is not found, -1 will be returned.
|
||
|
*
|
||
|
* @param key key where the data is stored
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @return -1, if the key is not found, the value after incrementing otherwise
|
||
|
*/
|
||
|
public long decr(String key, Object hash) {
|
||
|
return incrdecr("decr", key, 1, hash);
|
||
|
}
|
||
|
/** Decrement the value at the specified key by 1, and then return it.
|
||
|
*
|
||
|
* Note that the server uses a 32-bit unsigned integer, and checks for
|
||
|
* underflow. In the event of underflow, the result will be zero. Because
|
||
|
* Java lacks unsigned types, the value is returned as a 64-bit integer.
|
||
|
* The server will only decrement a value if it already exists;
|
||
|
* if a value is not found, -1 will be returned.
|
||
|
*
|
||
|
* @param key key where the data is stored
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @return -1, if the key is not found, the value after incrementing otherwise
|
||
|
*/
|
||
|
public long decr(String key, int hash) {
|
||
|
return incrdecr("decr", key, 1, new Integer(hash));
|
||
|
}
|
||
|
|
||
|
/** Decrement the value at the specified key by the specified increment,
|
||
|
* and then return it.
|
||
|
*
|
||
|
* Note that the server uses a 32-bit unsigned integer, and checks for
|
||
|
* underflow. In the event of underflow, the result will be zero. Because
|
||
|
* Java lacks unsigned types, the value is returned as a 64-bit integer.
|
||
|
* The server will only decrement a value if it already exists;
|
||
|
* if a value is not found, -1 will be returned.
|
||
|
*
|
||
|
* @param key key where the data is stored
|
||
|
* @param inc how much to increment by
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @return -1, if the key is not found, the value after incrementing otherwise
|
||
|
*/
|
||
|
public long decr(String key, long inc, int hash) {
|
||
|
return incrdecr("decr", key, inc, new Integer(hash));
|
||
|
}
|
||
|
/** Decrement the value at the specified key by 1, and then return it.
|
||
|
*
|
||
|
* Note that the server uses a 32-bit unsigned integer, and checks for
|
||
|
* underflow. In the event of underflow, the result will be zero. Because
|
||
|
* Java lacks unsigned types, the value is returned as a 64-bit integer.
|
||
|
* The server will only decrement a value if it already exists;
|
||
|
* if a value is not found, -1 will be returned.
|
||
|
*
|
||
|
* @param key key where the data is stored
|
||
|
* @param inc how much to increment by
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @return -1, if the key is not found, the value after incrementing otherwise
|
||
|
*/
|
||
|
public long decr(String key, long inc, Object hash) {
|
||
|
return incrdecr("decr", key, inc, hash);
|
||
|
}
|
||
|
|
||
|
private long incrdecr(String cmdname, String key, long inc, Object hash) {
|
||
|
SockIO sock;
|
||
|
if (hash != null) {
|
||
|
sock = get_sock(hash);
|
||
|
} else {
|
||
|
sock = get_sock(key);
|
||
|
}
|
||
|
|
||
|
if (sock == null) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
sock.writeBytes(cmdname + " " + key + " " + inc + "\r\n");
|
||
|
sock.flush();
|
||
|
String tmp = sock.readLine();
|
||
|
return Long.decode(tmp).longValue();
|
||
|
} catch (IOException e) {
|
||
|
sock.close();
|
||
|
} catch (NumberFormatException e) {
|
||
|
}
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/** Retrieve a key from the server.
|
||
|
*
|
||
|
* If the data was compressed or serialized when compressed, it will automatically
|
||
|
* be decompressed or serialized, as appropriate. (Inclusive or)
|
||
|
*
|
||
|
* Non-serialized data will be returned as a string, so explicit conversion to
|
||
|
* numeric types will be necessary, if desired
|
||
|
*
|
||
|
* @param key key where data is stored
|
||
|
* @return the object that was previously stored, or null if it was not previously stored
|
||
|
*/
|
||
|
public Object get(String key) {
|
||
|
return get(key, null);
|
||
|
}
|
||
|
|
||
|
/** Retrieve a key from the server, using a specific hash.
|
||
|
*
|
||
|
* If the data was compressed or serialized when compressed, it will automatically
|
||
|
* be decompressed or serialized, as appropriate. (Inclusive or)
|
||
|
*
|
||
|
* Non-serialized data will be returned as a string, so explicit conversion to
|
||
|
* numeric types will be necessary, if desired
|
||
|
*
|
||
|
* @param key key where data is stored
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @return the object that was previously stored, or null if it was not previously stored
|
||
|
*/
|
||
|
public Object get(String key, int hash) {
|
||
|
return get(key, new Integer(hash));
|
||
|
}
|
||
|
|
||
|
/** Retrieve a key from the server, using a specific hash.
|
||
|
*
|
||
|
* If the data was compressed or serialized when compressed, it will automatically
|
||
|
* be decompressed or serialized, as appropriate. (Inclusive or)
|
||
|
*
|
||
|
* Non-serialized data will be returned as a string, so explicit conversion to
|
||
|
* numeric types will be necessary, if desired
|
||
|
*
|
||
|
* @param key key where data is stored
|
||
|
* @param hash used to determine which server is responsible for the specified key
|
||
|
* @return the object that was previously stored, or null if it was not previously stored
|
||
|
*/
|
||
|
public Object get(String key, Object hash) {
|
||
|
SockIO sock;
|
||
|
if (hash != null) {
|
||
|
sock = get_sock(hash);
|
||
|
} else {
|
||
|
sock = get_sock(key);
|
||
|
}
|
||
|
|
||
|
if (sock == null) {
|
||
|
return null;
|
||
|
}
|
||
|
try {
|
||
|
sock.writeBytes("get " + key + "\r\n");
|
||
|
} catch (IOException e) {
|
||
|
sock.close();
|
||
|
}
|
||
|
HashMap hm = new HashMap();
|
||
|
load_items(sock, hm);
|
||
|
if (debug) {
|
||
|
Iterator i = hm.entrySet().iterator();
|
||
|
while (i.hasNext()) {
|
||
|
Entry e = (Entry)i.next();
|
||
|
System.out.println("MemCache: got " + e.getKey() + " = " + e.getValue());
|
||
|
}
|
||
|
}
|
||
|
return hm.get(key);
|
||
|
}
|
||
|
|
||
|
|
||
|
/** Retrieve multiple keys from the memcache.
|
||
|
*
|
||
|
* This is recommended over repeated calls to {@link #get(String) get()}, since it
|
||
|
* is more efficient.
|
||
|
* @return a hashmap with entries for each key is found by the server,
|
||
|
* keys that are not found are not entered into the hashmap, but attempting to
|
||
|
* retrieve them from the hashmap gives you null.
|
||
|
* @param keys keys to retrieve
|
||
|
*/
|
||
|
public HashMap get_multi(String[] keys) {
|
||
|
return get_multi(keys, null);
|
||
|
}
|
||
|
|
||
|
/** Retrieve multiple keys from the memcache.
|
||
|
*
|
||
|
* This is recommended over repeated calls to {@link #get(String) get()}, since it
|
||
|
* is more efficient.
|
||
|
* @param keys keys to retrieve
|
||
|
* @param hashes hash values used to determine which server to use for each key;
|
||
|
* if a hash is not provided for a key (either because there are more keys
|
||
|
* than hashes, or a hash value is specifically null) a hash will be computed.
|
||
|
* @return a hashmap with entries for each key is found by the server,
|
||
|
* keys that are not found are not entered into the hashmap, but attempting to
|
||
|
* retrieve them from the hashmap gives you null.
|
||
|
*/
|
||
|
public HashMap get_multi(String[] keys, Object[] hashes) {
|
||
|
ArrayList socks = new ArrayList();
|
||
|
HashMap sock_keys = new HashMap();
|
||
|
for (int i = 0; i < keys.length; ++i) {
|
||
|
SockIO sock;
|
||
|
if (hashes!= null && hashes.length > i && hashes[i] != null) {
|
||
|
sock = get_sock(hashes[i]);
|
||
|
} else {
|
||
|
sock = get_sock(keys[i]);
|
||
|
}
|
||
|
if (sock == null) {
|
||
|
continue;
|
||
|
}
|
||
|
if (!sock_keys.containsKey(sock)) {
|
||
|
sock_keys.put(sock, new StringBuffer());
|
||
|
socks.add(sock);
|
||
|
}
|
||
|
((StringBuffer)sock_keys.get(sock)).append(" " + keys[i]);
|
||
|
}
|
||
|
|
||
|
// Pass 1: send out requests
|
||
|
|
||
|
ArrayList gather = new ArrayList();
|
||
|
Iterator i = socks.iterator();
|
||
|
while (i.hasNext()) {
|
||
|
SockIO sock =null;
|
||
|
try {
|
||
|
sock = (SockIO) i.next();
|
||
|
sock.writeBytes("get" + (StringBuffer)sock_keys.get(sock) + "\r\n");
|
||
|
sock.flush();
|
||
|
gather.add(sock);
|
||
|
} catch (IOException e) {
|
||
|
sock.close();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
HashMap ret = new HashMap();
|
||
|
// Pass 2: get results
|
||
|
i = gather.iterator();
|
||
|
while (i.hasNext()) {
|
||
|
load_items((SockIO) i.next(), ret);
|
||
|
}
|
||
|
|
||
|
if (debug) {
|
||
|
i = ret.entrySet().iterator();
|
||
|
while (i.hasNext()) {
|
||
|
Entry e = (Entry)i.next();
|
||
|
System.out.println("MemCache: got " + e.getKey() + " = " + e.getValue());
|
||
|
}
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
private void load_items(SockIO sock, HashMap hm) {
|
||
|
try {
|
||
|
while (true) {
|
||
|
String line = sock.readLine();
|
||
|
if (line == null) {
|
||
|
return;
|
||
|
} else if (line.startsWith("VALUE")) {
|
||
|
StreamTokenizer st = new StreamTokenizer(new StringReader(line));
|
||
|
st.ordinaryChars(48, 57);
|
||
|
st.wordChars(48, 57); // add numbers to words
|
||
|
st.nextToken(); // skip VALUE token
|
||
|
st.nextToken();
|
||
|
String key = st.sval;
|
||
|
st.nextToken();
|
||
|
int flag = new Integer(st.sval).intValue();
|
||
|
st.nextToken();
|
||
|
int length = new Integer(st.sval).intValue();
|
||
|
byte[] buf = new byte[length];
|
||
|
sock.readFully(buf); // blocking read
|
||
|
|
||
|
sock.readLine(); // clear out \r\n that should be left
|
||
|
// check for compression
|
||
|
Object o;
|
||
|
|
||
|
if ((flag & F_COMPRESSED) != 0) {
|
||
|
try {
|
||
|
GZIPInputStream gzi = new GZIPInputStream(new ByteArrayInputStream(buf));
|
||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream(buf.length);
|
||
|
// read the input stream, and write to a byte array output stream since
|
||
|
// we have to read into a byte array, but we don't know how large it
|
||
|
// will need to be, and we don't want to resize it a bunch
|
||
|
|
||
|
byte[] tmp = new byte[1024];
|
||
|
int count;
|
||
|
while ((count = gzi.read(tmp)) != -1) {
|
||
|
bos.write(tmp,0,count);
|
||
|
}
|
||
|
|
||
|
buf = bos.toByteArray();
|
||
|
} catch (IOException e) {
|
||
|
}
|
||
|
}
|
||
|
if ((flag & F_SERIALIZED) != 0) {
|
||
|
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(buf));
|
||
|
try {
|
||
|
o = ois.readObject();
|
||
|
if (debug) {
|
||
|
System.out.println("MemCache: Deserializing " + o.getClass().getName());
|
||
|
}
|
||
|
} catch (ClassNotFoundException e) {
|
||
|
o = new String(buf);
|
||
|
}
|
||
|
} else {
|
||
|
o = new String(buf);
|
||
|
}
|
||
|
hm.put(key, o);
|
||
|
} else if (line.equals("END")) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
} catch (NumberFormatException e) {
|
||
|
//this shouldn't happen...
|
||
|
System.out.println("MemCache: The sever passed us bad numbers in get");
|
||
|
} catch (IOException e) {
|
||
|
sock.close();
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|