package org.infinispan.server.test.client.memcached; import static org.junit.Assert.assertEquals; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; import java.net.SocketTimeoutException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.apache.log4j.Logger; import org.junit.Assert; /** * A Really simple Memcached client/helper. * * @author <a href="mailto:mlinhard@redhat.com">Michal Linhard</a> * @version May 2011 */ public class MemcachedClient { private static final Logger log = Logger.getLogger(MemcachedClient.class); public static final int DEFAULT_MEMCACHED_PORT = 11211; public static final int DEFAULT_TIMEOUT = 10000; /** * is there any standard replacement for this ? something that can read both binary data and also strings when needed. */ public static class StringAndBytesReader { private InputStream input; private String encoding; private byte[] TEMP = new byte[1]; public StringAndBytesReader(InputStream input, String encoding) { super(); this.input = input; this.encoding = encoding; } public byte[] read(int len) throws IOException { try { byte[] ret = new byte[len]; input.read(ret, 0, len); return ret; } catch (SocketTimeoutException ste) { Assert.fail("Read timeout"); return null; } } public byte read() throws IOException { try { input.read(TEMP, 0, 1); return TEMP[0]; } catch (SocketTimeoutException ste) { Assert.fail("Read timeout"); return -1; } } public String readln() throws IOException { byte[] buf = new byte[512]; int maxlen = 512; int read = 0; buf[read] = read(); while (buf[read] != '\n') { read++; if (read == maxlen) { maxlen += 512; buf = Arrays.copyOf(buf, maxlen); } buf[read] = read(); } if (read == 0) { return ""; } if (buf[read - 1] == '\r') { read--; } buf = Arrays.copyOf(buf, read); String ret = new String(buf, encoding); if (log.isTraceEnabled()) { log.trace("<< \"" + ret + "\""); } return ret; } } private String encoding; private Socket socket; private PrintWriter out; private StringAndBytesReader in; public MemcachedClient() throws IOException { this("UTF-8", "localhost", DEFAULT_MEMCACHED_PORT, DEFAULT_TIMEOUT); } public MemcachedClient(String enc, String host, int port, int timeout) throws IOException { encoding = enc; socket = new Socket(host, port); socket.setSoTimeout(timeout); out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), encoding)); in = new StringAndBytesReader(socket.getInputStream(), encoding); } public MemcachedClient(String host, int port) throws IOException { this("UTF-8", host, port, DEFAULT_TIMEOUT); } public void writeln(String str) { out.print(str + "\r\n"); if (log.isTraceEnabled()) { log.trace(">> \"" + str + "\""); } } public void write(byte[] data) throws IOException { if (log.isTraceEnabled()) { log.trace(">> " + data.length + " bytes"); } socket.getOutputStream().write(data); } public String readln() throws IOException { return in.readln(); } public void flush() { out.flush(); } public void close() throws IOException { socket.close(); } /** * Returns the value for the key. */ public String get(String key) throws IOException { byte[] data = getBytes(key); if (data == null) { return null; } return new String(data, encoding); } /** * Returns the value for the key. */ public byte[] getBytes(String key) throws IOException { writeln("get " + key); flush(); String valueStr = readln(); if ("END".equals(valueStr)) { return null; } if (valueStr.startsWith("VALUE")) { String[] value = valueStr.split(" "); assertEquals(key, value[1]); int size = new Integer(value[3]); byte[] ret = in.read(size); assertEquals('\r', in.read()); assertEquals('\n', in.read()); assertEquals("END", readln()); return ret; } else { return null; } } public void set(String key, String value) throws IOException { writeln("set " + key + " 0 0 " + value.getBytes(encoding).length); writeln(value); flush(); assertEquals("STORED", readln()); } public void set(String key, String value, int lifespan, int maxidle) throws IOException { writeln("set " + key + " " + lifespan + " " + maxidle + " " + value.getBytes(encoding).length); writeln(value); flush(); assertEquals("STORED", readln()); } public void setNoReadln(String key, String value) throws IOException { writeln("set " + key + " 0 0 " + value.getBytes(encoding).length); writeln(value); flush(); } /** * returns "DELETED" or "NOT_FOUND" depending whether the key existed. */ public String delete(String key) throws IOException { writeln("delete " + key); flush(); return readln(); } public Map<String, String> getStats() throws IOException { writeln("stats"); flush(); String statline = readln(); Map<String, String> stats = new HashMap<String, String>(); while (statline.startsWith("STAT")) { String[] stat = statline.split(" "); stats.put(stat[1], stat[2]); statline = readln(); } assertEquals("END", statline); return stats; } /** * returns server time retrieved via stats command -1 if time stat is not returned. */ public long getServerTime() throws IOException { writeln("stats"); flush(); String statline = readln(); long time = -1; while (statline.startsWith("STAT")) { String[] stat = statline.split(" "); if (stat[1].equals("time")) { time = new Long(stat[2]); } statline = readln(); } assertEquals("END", statline); return time; } public String getCasId(String aKey) throws IOException { writeln("gets " + aKey); flush(); String[] valueline = readln().split(" "); assertEquals("VALUE", valueline[0]); assertEquals(aKey, valueline[1]); in.read(new Integer(valueline[3])); assertEquals("", readln()); assertEquals("END", readln()); return valueline[4]; } }