All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.schooner.MemCached.AscIIClient Maven / Gradle / Ivy

There is a newer version: 3.0.2
Show newest version
/*******************************************************************************
 * Copyright (c) 2009 Schooner Information Technology, Inc.
 * All rights reserved.
 * 
 * http://www.schoonerinfotech.com/
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ******************************************************************************/
package com.schooner.MemCached;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;

import com.whalin.MemCached.ErrorHandler;
import com.whalin.MemCached.LineInputStream;
import com.whalin.MemCached.MemCachedClient;

/**
 * This client implements the text protocol of memcached in a very high
 * performance way.
*
* Please use the wrapper class {@link MemCachedClient} for accessing the * memcached server. * * @author Xingen Wang * @since 2.5.0 * @see BinaryClient */ public class AscIIClient extends MemCachedClient { private TransCoder transCoder = new ObjectTransCoder(); // pool instance private SchoonerSockIOPool pool; // which pool to use private String poolName; // flags private boolean sanitizeKeys; private boolean primitiveAsString; @SuppressWarnings("unused") private boolean compressEnable; @SuppressWarnings("unused") private long compressThreshold; private String defaultEncoding; public boolean isUseBinaryProtocol() { return false; } /** * Creates a new instance of MemCachedClient. */ public AscIIClient() { this(null); } /** * Creates a new instance of MemCachedClient accepting a passed in pool * name. * * @param poolName * name of SockIOPool * @param binaryProtocal * whether use binary protocol. */ public AscIIClient(String poolName) { super((MemCachedClient) null); this.poolName = poolName; init(); } public AscIIClient(String poolName, ClassLoader cl, ErrorHandler eh) { super((MemCachedClient) null); this.poolName = poolName; this.classLoader = cl; this.errorHandler = eh; init(); } /** * Initializes client object to defaults. * * This enables compression and sets compression threshhold to 15 KB. */ private void init() { this.sanitizeKeys = true; this.primitiveAsString = false; this.compressEnable = false; this.compressThreshold = COMPRESS_THRESH; this.defaultEncoding = "UTF-8"; this.poolName = (this.poolName == null) ? "default" : this.poolName; // get a pool instance to work with for the life of this instance this.pool = SchoonerSockIOPool.getInstance(poolName); } public boolean keyExists(String key) { return (this.get(key, null) != null); } public boolean delete(String key) { return delete(key, null, null); } public boolean delete(String key, Date expiry) { return delete(key, null, expiry); } public boolean delete(String key, Integer hashCode, Date expiry) { if (key == null) { if (log.isErrorEnabled()) log.error("null value for key passed to delete()"); return false; } try { key = sanitizeKey(key); } catch (UnsupportedEncodingException e) { // if we have an errorHandler, use its hook if (errorHandler != null) errorHandler.handleErrorOnDelete(this, e, key); if (log.isErrorEnabled()) log.error("failed to sanitize your key!", e); return false; } // get SockIO obj from hash or from key SchoonerSockIO sock = pool.getSock(key, hashCode); // return false if unable to get SockIO obj if (sock == null) { if (errorHandler != null) errorHandler.handleErrorOnDelete(this, new IOException("no socket to server available"), key); return false; } // build command StringBuilder command = new StringBuilder("delete ").append(key); if (expiry != null) command.append(" " + expiry.getTime() / 1000); command.append("\r\n"); try { sock.write(command.toString().getBytes()); // if we get appropriate response back, then we return true // get result code String line = new SockInputStream(sock, Integer.MAX_VALUE).getLine(); if (DELETED.equals(line)) { // successful if (log.isDebugEnabled()) log.debug(new StringBuffer().append("++++ deletion of key: ").append(key) .append(" from cache was a success").toString()); return true; } else if (NOTFOUND.equals(line)) { // key not found if (log.isDebugEnabled()) log.debug(new StringBuffer().append("++++ deletion of key: ").append(key) .append(" from cache failed as the key was not found").toString()); } else { // other error information if (log.isErrorEnabled()) { log.error(new StringBuffer().append("++++ error deleting key: ").append(key).toString()); log.error(new StringBuffer().append("++++ server response: ").append(line).toString()); } } } catch (IOException e) { // if we have an errorHandler, use its hook if (errorHandler != null) errorHandler.handleErrorOnDelete(this, e, key); // exception thrown if (log.isErrorEnabled()) { log.error("++++ exception thrown while writing bytes to server on delete"); log.error(e.getMessage(), e); } try { sock.sockets.invalidateObject(sock); } catch (Exception e1) { if (log.isErrorEnabled()) log.error("++++ failed to close socket : " + sock.toString()); } sock = null; } finally { if (sock != null) { sock.close(); sock = null; } } return false; } public boolean set(String key, Object value) { return set("set", key, value, null, null, 0L, primitiveAsString); } public boolean set(String key, Object value, Integer hashCode) { return set("set", key, value, null, hashCode, 0L, primitiveAsString); } public boolean set(String key, Object value, Date expiry) { return set("set", key, value, expiry, null, 0L, primitiveAsString); } public boolean set(String key, Object value, Date expiry, Integer hashCode) { return set("set", key, value, expiry, hashCode, 0L, primitiveAsString); } public boolean add(String key, Object value) { return set("add", key, value, null, null, 0L, primitiveAsString); } public boolean add(String key, Object value, Integer hashCode) { return set("add", key, value, null, hashCode, 0L, primitiveAsString); } public boolean add(String key, Object value, Date expiry) { return set("add", key, value, expiry, null, 0L, primitiveAsString); } public boolean add(String key, Object value, Date expiry, Integer hashCode) { return set("add", key, value, expiry, hashCode, 0L, primitiveAsString); } public boolean append(String key, Object value, Integer hashCode) { return set("append", key, value, null, hashCode, 0L, primitiveAsString); } public boolean append(String key, Object value) { return set("append", key, value, null, null, 0L, primitiveAsString); } public boolean cas(String key, Object value, Integer hashCode, long casUnique) { return set("cas", key, value, null, hashCode, casUnique, primitiveAsString); } public boolean cas(String key, Object value, Date expiry, long casUnique) { return set("cas", key, value, expiry, null, casUnique, primitiveAsString); } public boolean cas(String key, Object value, Date expiry, Integer hashCode, long casUnique) { return set("cas", key, value, expiry, hashCode, casUnique, primitiveAsString); } public boolean cas(String key, Object value, long casUnique) { return set("cas", key, value, null, null, casUnique, primitiveAsString); } public boolean prepend(String key, Object value, Integer hashCode) { return set("prepend", key, value, null, hashCode, 0L, primitiveAsString); } public boolean prepend(String key, Object value) { return set("prepend", key, value, null, null, 0L, primitiveAsString); } public boolean replace(String key, Object value) { return set("replace", key, value, null, null, 0L, primitiveAsString); } public boolean replace(String key, Object value, Integer hashCode) { return set("replace", key, value, null, hashCode, 0L, primitiveAsString); } public boolean replace(String key, Object value, Date expiry) { return set("replace", key, value, expiry, null, 0L, primitiveAsString); } public boolean replace(String key, Object value, Date expiry, Integer hashCode) { return set("replace", key, value, expiry, hashCode, 0L, primitiveAsString); } /** * Stores data to cache. * * 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 compression is enabled, and the data is longer than the compression * threshold
* the data will be stored in compressed form.
*
* As of the current release, all objects stored will use java * serialization. * * @param cmdname * action to take (set, add, replace) * @param key * key to store cache under * @param value * object to cache * @param expiry * expiration * @param hashCode * if not null, then the int hashcode to use * @return true/false indicating success */ private boolean set(String cmdname, String key, Object value, Date expiry, Integer hashCode, Long casUnique, boolean asString) { if (cmdname == null || key == null) { if (log.isErrorEnabled()) log.error("key is null or cmd is null/empty for set()"); return false; } try { key = sanitizeKey(key); } catch (UnsupportedEncodingException e) { // if we have an errorHandler, use its hook if (errorHandler != null) errorHandler.handleErrorOnSet(this, e, key); if (log.isErrorEnabled()) log.error("failed to sanitize your key!", e); return false; } if (value == null) { if (log.isErrorEnabled()) log.error("trying to store a null value to cache"); return false; } // get SockIO obj SchoonerSockIO sock = pool.getSock(key, hashCode); if (sock == null) { if (errorHandler != null) errorHandler.handleErrorOnSet(this, new IOException("no socket to server available"), key); return false; } if (expiry == null) expiry = new Date(0); // store flags int flags = NativeHandler.getMarkerFlag(value); // construct the command String cmd = new StringBuffer().append(cmdname).append(" ").append(key).append(" ").append(flags).append(" ") .append(expiry.getTime() / 1000).append(" ").toString(); try { sock.writeBuf.clear(); sock.writeBuf.put(cmd.getBytes()); int offset = sock.writeBuf.position(); // write blank bytes size. sock.writeBuf.put(BLAND_DATA_SIZE); if (casUnique != 0) sock.writeBuf.put((" " + casUnique.toString()).getBytes()); sock.writeBuf.put(B_RETURN); SockOutputStream output = new SockOutputStream(sock); int valLen = 0; if (flags != MARKER_OTHERS) { byte[] b; if (asString) { b = value.toString().getBytes(defaultEncoding); } else { /* * Using NativeHandler to serialize the value */ b = NativeHandler.encode(value); } output.write(b); valLen = b.length; } else { /* * Using default object transcoder to serialize the * non-primitive values. */ valLen = transCoder.encode(output, value); } sock.writeBuf.put(B_RETURN); // write serialized object byte[] objectSize = new Integer(valLen).toString().getBytes(); int oldPosition = sock.writeBuf.position(); sock.writeBuf.position(offset); // put real object bytes size sock.writeBuf.put(objectSize); // return to correct position. sock.writeBuf.position(oldPosition); // write the buffer to server // now write the data to the cache server sock.flush(); // get result code String line = new SockInputStream(sock, Integer.MAX_VALUE).getLine(); if (STORED.equals(line)) { /* * Successfully set here. */ return true; } } catch (Exception e) { // if we have an errorHandler, use its hook if (errorHandler != null) errorHandler.handleErrorOnSet(this, e, key); // exception thrown if (log.isErrorEnabled()) { log.error("++++ exception thrown while writing bytes to server on set"); log.error(e.getMessage(), e); } try { sock.sockets.invalidateObject(sock); } catch (Exception e1) { if (log.isErrorEnabled()) log.error("++++ failed to close socket : " + sock.toString()); } sock = null; } finally { if (sock != null) { sock.close(); sock = null; } } return false; } public long addOrIncr(String key) { return addOrIncr(key, 0, null); } public long addOrIncr(String key, long inc) { return addOrIncr(key, inc, null); } public long addOrIncr(String key, long inc, Integer hashCode) { boolean ret = add(key, "" + inc, hashCode); if (ret) { return inc; } else { return incrdecr("incr", key, inc, hashCode); } } public long addOrDecr(String key) { return addOrDecr(key, 0, null); } public long addOrDecr(String key, long inc) { return addOrDecr(key, inc, null); } public long addOrDecr(String key, long inc, Integer hashCode) { boolean ret = add(key, "" + inc, hashCode); if (ret) { return inc; } else { return incrdecr("decr", key, inc, hashCode); } } public long incr(String key) { return incrdecr("incr", key, 1, null); } public long incr(String key, long inc) { return incrdecr("incr", key, inc, null); } public long incr(String key, long inc, Integer hashCode) { return incrdecr("incr", key, inc, hashCode); } public long decr(String key) { return incrdecr("decr", key, 1, null); } public long decr(String key, long inc) { return incrdecr("decr", key, inc, null); } public long decr(String key, long inc, Integer hashCode) { return incrdecr("decr", key, inc, hashCode); } /** * Increments/decrements the value at the specified key by inc. * * 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 cmdname * increment/decrement * @param key * cache key * @param inc * amount to incr or decr * @param hashCode * if not null, then the int hashcode to use * @return new value or -1 if not exist */ private long incrdecr(String cmdname, String key, long inc, Integer hashCode) { if (key == null) { if (log.isErrorEnabled()) log.error("null key for incrdecr()"); return -1; } try { key = sanitizeKey(key); } catch (UnsupportedEncodingException e) { // if we have an errorHandler, use its hook if (errorHandler != null) errorHandler.handleErrorOnGet(this, e, key); if (log.isErrorEnabled()) log.error("failed to sanitize your key!", e); return -1; } // get SockIO obj for given cache key SchoonerSockIO sock = pool.getSock(key, hashCode); if (sock == null) { if (errorHandler != null) errorHandler.handleErrorOnSet(this, new IOException("no socket to server available"), key); return -1; } try { String cmd = new StringBuffer().append(cmdname).append(" ").append(key).append(" ").append(inc) .append("\r\n").toString(); sock.write(cmd.getBytes()); // get result code String line = new SockInputStream(sock, Integer.MAX_VALUE).getLine().split("\r\n")[0]; if (line.matches("\\d+")) { // Sucessfully increase. // return sock to pool and return result return Long.parseLong(line); } else if (NOTFOUND.equals(line + "\r\n")) { if (log.isInfoEnabled()) log.info(new StringBuffer().append("++++ key not found to incr/decr for key: ").append(key) .toString()); } else { if (log.isErrorEnabled()) { log.error(new StringBuffer().append("++++ error incr/decr key: ").append(key).toString()); log.error(new StringBuffer().append("++++ server response: ").append(line).toString()); } } } catch (Exception e) { // if we have an errorHandler, use its hook if (errorHandler != null) errorHandler.handleErrorOnGet(this, e, key); // exception thrown if (log.isErrorEnabled()) { log.error("++++ exception thrown while writing bytes to server on incr/decr"); log.error(e.getMessage(), e); } try { sock.sockets.invalidateObject(sock); } catch (Exception e1) { if (log.isErrorEnabled()) log.error("++++ failed to close socket : " + sock.toString()); } sock = null; } finally { if (sock != null) { sock.close(); sock = null; } } return -1; } public Object get(String key) { return get(key, null); } public Object get(String key, Integer hashCode) { return get("get", key, hashCode, false); } public MemcachedItem gets(String key) { return gets(key, null); } public MemcachedItem gets(String key, Integer hashCode) { return gets("gets", key, hashCode, false); } /** * 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 hashCode * if not null, then the int hashcode to use * @param asString * if true, then return string val * @return the object that was previously stored, or null if it was not * previously stored */ public Object get(String key, Integer hashCode, boolean asString) { return get("get", key, hashCode, asString); } /** * get memcached item from server. * * @param cmd * cmd to be used, get/gets * @param key * specified key * @param hashCode * specified hashcode * @return memcached item with value in it. */ private Object get(String cmd, String key, Integer hashCode, boolean asString) { if (key == null) { if (log.isErrorEnabled()) log.error("key is null for get()"); return null; } try { key = sanitizeKey(key); } catch (UnsupportedEncodingException e) { if (log.isErrorEnabled()) log.error("failed to sanitize your key!", e); return null; } // get SockIO obj using cache key SchoonerSockIO sock = pool.getSock(key, hashCode); if (sock == null) { if (errorHandler != null) errorHandler.handleErrorOnGet(this, new IOException("no socket to server available"), key); return null; } String cmdLine = cmd + " " + key; try { sock.writeBuf.clear(); sock.writeBuf.put(cmdLine.getBytes()); sock.writeBuf.put(B_RETURN); // write buffer to server sock.flush(); int dataSize = 0; int flag = 0; // get result code SockInputStream input = new SockInputStream(sock, Integer.MAX_VALUE); // Then analysis the return metadata from server // including key, flag and data size boolean stop = false; StringBuffer sb = new StringBuffer(); int b; int index = 0; while (!stop) { /* * Critical block to parse the response header. */ b = input.read(); if (b == ' ' || b == '\r') { switch (index) { case 0: if (END.startsWith(sb.toString())) return null; case 1: break; case 2: flag = Integer.parseInt(sb.toString()); break; case 3: // get the data size dataSize = Integer.parseInt(sb.toString()); break; } index++; sb = new StringBuffer(); if (b == '\r') { input.read(); stop = true; } continue; } sb.append((char) b); } Object o = null; input.willRead(dataSize); // we can only take out serialized objects if (dataSize > 0) { if (NativeHandler.isHandled(flag)) { // decoding object byte[] buf = input.getBuffer(); if ((flag & F_COMPRESSED) == F_COMPRESSED) { GZIPInputStream gzi = new GZIPInputStream(new ByteArrayInputStream(buf)); ByteArrayOutputStream bos = new ByteArrayOutputStream(buf.length); int count; byte[] tmp = new byte[2048]; while ((count = gzi.read(tmp)) != -1) { bos.write(tmp, 0, count); } // store uncompressed back to buffer buf = bos.toByteArray(); gzi.close(); } if (primitiveAsString || asString) { o = new String(buf, defaultEncoding); } else o = NativeHandler.decode(buf, flag); } else if (transCoder != null) { // decode object with default transcoder. InputStream in = input; if ((flag & F_COMPRESSED) == F_COMPRESSED) in = new GZIPInputStream(in); if (classLoader == null) o = transCoder.decode(in); else o = ((ObjectTransCoder) transCoder).decode(in, classLoader); } } input.willRead(Integer.MAX_VALUE); // Skip "\r\n" after each data block for VALUE input.getLine(); // Skip "END\r\n" after get input.getLine(); return o; } catch (Exception ce) { // if we have an errorHandler, use its hook if (errorHandler != null) errorHandler.handleErrorOnGet(this, ce, key); // exception thrown if (log.isErrorEnabled()) { log.error("++++ exception thrown while trying to get object from cache for key: " + key); log.error(ce.getMessage(), ce); } try { sock.sockets.invalidateObject(sock); } catch (Exception e1) { if (log.isErrorEnabled()) log.error("++++ failed to close socket : " + sock.toString()); } sock = null; } finally { if (sock != null) { sock.close(); sock = null; } } return null; } public MemcachedItem gets(String cmd, String key, Integer hashCode, boolean asString) { if (key == null) { if (log.isErrorEnabled()) log.error("key is null for get()"); return null; } try { key = sanitizeKey(key); } catch (UnsupportedEncodingException e) { if (log.isErrorEnabled()) log.error("failed to sanitize your key!", e); return null; } // get SockIO obj using cache key SchoonerSockIO sock = pool.getSock(key, hashCode); if (sock == null) { if (errorHandler != null) errorHandler.handleErrorOnGet(this, new IOException("no socket to server available"), key); return null; } String cmdLine = cmd + " " + key; try { sock.writeBuf.clear(); sock.writeBuf.put(cmdLine.getBytes()); sock.writeBuf.put(B_RETURN); // write buffer to server sock.flush(); int dataSize = 0; int flag = 0; MemcachedItem item = new MemcachedItem(); // get result code SockInputStream input = new SockInputStream(sock, Integer.MAX_VALUE); // Then analysis the return metadata from server // including key, flag and data size boolean stop = false; StringBuffer sb = new StringBuffer(); int b; int index = 0; while (!stop) { /* * Critical block to parse the response header. */ b = input.read(); if (b == ' ' || b == '\r') { switch (index) { case 0: if (END.startsWith(sb.toString())) return null; case 1: break; case 2: flag = Integer.parseInt(sb.toString()); break; case 3: // get the data size dataSize = Integer.parseInt(sb.toString()); break; case 4: if (cmd.equals("gets")) item.casUnique = Long.parseLong(sb.toString()); break; } index++; sb = new StringBuffer(); if (b == '\r') { input.read(); stop = true; } continue; } sb.append((char) b); } Object o = null; input.willRead(dataSize); // we can only take out serialized objects if (dataSize > 0) { if (NativeHandler.isHandled(flag)) { byte[] buf = input.getBuffer(); if ((flag & F_COMPRESSED) == F_COMPRESSED) { GZIPInputStream gzi = new GZIPInputStream(new ByteArrayInputStream(buf)); ByteArrayOutputStream bos = new ByteArrayOutputStream(buf.length); int count; byte[] tmp = new byte[2048]; while ((count = gzi.read(tmp)) != -1) { bos.write(tmp, 0, count); } // store uncompressed back to buffer buf = bos.toByteArray(); gzi.close(); } if (primitiveAsString || asString) { o = new String(buf, defaultEncoding); } else // decoding object o = NativeHandler.decode(buf, flag); } else if (transCoder != null) { InputStream in = input; if ((flag & F_COMPRESSED) == F_COMPRESSED) in = new GZIPInputStream(in); // decode object with default transcoder. o = transCoder.decode(in); } } item.value = o; input.willRead(Integer.MAX_VALUE); // Skip "\r\n" after each data block for VALUE input.getLine(); // Skip "END\r\n" after get input.getLine(); return item; } catch (Exception ce) { // if we have an errorHandler, use its hook if (errorHandler != null) errorHandler.handleErrorOnGet(this, ce, key); // exception thrown if (log.isErrorEnabled()) { log.error("++++ exception thrown while trying to get object from cache for key: " + key); log.error(ce.getMessage(), ce); } try { sock.sockets.invalidateObject(sock); } catch (Exception e1) { if (log.isErrorEnabled()) log.error("++++ failed to close socket : " + sock.toString()); } sock = null; } finally { if (sock != null) { sock.close(); sock = null; } } return null; } /** * set transcoder. TransCoder is used to customize the serialization and * deserialization. * * @param transCoder */ public void setTransCoder(TransCoder transCoder) { this.transCoder = transCoder; } public Object[] getMultiArray(String[] keys) { return getMultiArray(keys, null); } public Object[] getMultiArray(String[] keys, Integer[] hashCodes) { Map data = getMulti(keys, hashCodes); if (data == null) return null; Object[] res = new Object[keys.length]; for (int i = 0; i < keys.length; i++) { res[i] = data.get(keys[i]); } return res; } /** * Retrieve multiple objects from the memcache. * * This is recommended over repeated calls to {@link #get(String) get()}, * since it
* is more efficient.
* * @param keys * String array of keys to retrieve * @param hashCodes * if not null, then the Integer array of hashCodes * @param asString * if true, retrieve string vals * @return Object array ordered in same order as key array containing * results */ public Object[] getMultiArray(String[] keys, Integer[] hashCodes, boolean asString) { Map data = getMulti(keys, hashCodes, asString); if (data == null) return null; Object[] res = new Object[keys.length]; for (int i = 0; i < keys.length; i++) { res[i] = data.get(keys[i]); } return res; } public Map getMulti(String[] keys) { return getMulti(keys, null); } public Map getMulti(String[] keys, Integer[] hashCodes) { return getMulti(keys, hashCodes, false); } /** * 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 hashCodes * if not null, then the Integer array of hashCodes * @param asString * if true then retrieve using String val * @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 Map getMulti(String[] keys, Integer[] hashCodes, boolean asString) { if (keys == null || keys.length == 0) { if (log.isErrorEnabled()) log.error("missing keys for getMulti()"); return null; } Map cmdMap = new HashMap(); String[] cleanKeys = new String[keys.length]; for (int i = 0; i < keys.length; ++i) { String key = keys[i]; if (key == null) { if (log.isErrorEnabled()) log.error("null key, so skipping"); continue; } Integer hash = null; if (hashCodes != null && hashCodes.length > i) hash = hashCodes[i]; cleanKeys[i] = key; try { cleanKeys[i] = sanitizeKey(key); } catch (UnsupportedEncodingException e) { // if we have an errorHandler, use its hook if (errorHandler != null) errorHandler.handleErrorOnGet(this, e, key); if (log.isErrorEnabled()) log.error("failed to sanitize your key!", e); continue; } // get SockIO obj from cache key SchoonerSockIO sock = pool.getSock(cleanKeys[i], hash); if (sock == null) { if (errorHandler != null) errorHandler.handleErrorOnGet(this, new IOException("no socket to server available"), key); continue; } // store in map and list if not already if (!cmdMap.containsKey(sock.getHost())) cmdMap.put(sock.getHost(), new StringBuilder("get")); cmdMap.get(sock.getHost()).append(" " + cleanKeys[i]); // return to pool sock.close(); } if (log.isDebugEnabled()) log.debug("multi get socket count : " + cmdMap.size()); // now query memcache Map ret = new HashMap(keys.length); // now use new NIO implementation (new NIOLoader(this)).doMulti(asString, cmdMap, keys, ret); // fix the return array in case we had to rewrite any of the keys for (int i = 0; i < keys.length; ++i) { // if key!=cleanKey and result has cleankey if (!keys[i].equals(cleanKeys[i]) && ret.containsKey(cleanKeys[i])) { ret.put(keys[i], ret.get(cleanKeys[i])); ret.remove(cleanKeys[i]); } // backfill missing keys w/ null value // if (!ret.containsKey(keys[i])) // ret.put(keys[i], null); } if (log.isDebugEnabled()) log.debug("++++ memcache: got back " + ret.size() + " results"); return ret; } /** * This method loads the data from cache into a Map. * * Pass a SockIO object which is ready to receive data and a HashMap
* to store the results. * * @param sock * socket waiting to pass back data * @param hm * hashmap to store data into * @param asString * if true, and if we are using NativehHandler, return string val * @throws IOException * if io exception happens while reading from socket */ private void loadMulti(LineInputStream input, Map hm, boolean asString) throws IOException { while (true) { String line = input.readLine(); if (line.startsWith(VALUE)) { String[] info = line.split(" "); String key = info[1]; int flag = Integer.parseInt(info[2]); int length = Integer.parseInt(info[3]); // read obj into buffer byte[] buf = new byte[length]; input.read(buf); input.clearEOL(); // ready object Object o = null; // we can only take out serialized objects if ((flag & F_COMPRESSED) == F_COMPRESSED) { GZIPInputStream gzi = new GZIPInputStream(new ByteArrayInputStream(buf)); ByteArrayOutputStream bos = new ByteArrayOutputStream(buf.length); int count; byte[] tmp = new byte[2048]; while ((count = gzi.read(tmp)) != -1) { bos.write(tmp, 0, count); } // store uncompressed back to buffer buf = bos.toByteArray(); gzi.close(); } if (flag != MARKER_OTHERS) { if (primitiveAsString || asString) { // pulling out string value o = new String(buf, defaultEncoding); } else { // decoding object try { o = NativeHandler.decode(buf, flag); } catch (Exception e) { if (log.isErrorEnabled()) log.error("++++ Exception thrown while trying to deserialize for key: " + key + " -- " + e.getMessage()); e.printStackTrace(); } } } else if (transCoder != null) { o = transCoder.decode(new ByteArrayInputStream(buf)); } // store the object into the cache hm.put(key, o); } else if (END.equals(line)) { break; } } } public boolean flushAll() { return flushAll(null); } public boolean flushAll(String[] servers) { // get SockIOPool instance // return false if unable to get SockIO obj if (pool == null) { if (log.isErrorEnabled()) log.error("++++ unable to get SockIOPool instance"); return false; } // get all servers and iterate over them servers = (servers == null) ? pool.getServers() : servers; // if no servers, then return early if (servers == null || servers.length <= 0) { if (log.isErrorEnabled()) log.error("++++ no servers to flush"); return false; } boolean success = true; for (int i = 0; i < servers.length; i++) { SchoonerSockIO sock = pool.getConnection(servers[i]); if (sock == null) { if (log.isErrorEnabled()) log.error("++++ unable to get connection to : " + servers[i]); success = false; if (errorHandler != null) errorHandler.handleErrorOnFlush(this, new IOException("no socket to server available")); continue; } // build command String command = "flush_all\r\n"; try { sock.write(command.getBytes()); // if we get appropriate response back, then we return true // get result code String line = new SockInputStream(sock, Integer.MAX_VALUE).getLine(); success = (OK.equals(line)) ? success && true : false; } catch (IOException e) { // if we have an errorHandler, use its hook if (errorHandler != null) errorHandler.handleErrorOnFlush(this, e); // exception thrown if (log.isErrorEnabled()) { log.error("++++ exception thrown while writing bytes to server on flushAll"); log.error(e.getMessage(), e); } try { sock.sockets.invalidateObject(sock); } catch (Exception e1) { if (log.isErrorEnabled()) log.error("++++ failed to close socket : " + sock.toString()); } success = false; sock = null; } finally { if (sock != null) { sock.close(); sock = null; } } } return success; } public Map> stats() { return stats(null); } public Map> stats(String[] servers) { return stats(servers, "stats\r\n", STATS); } public Map> statsItems() { return statsItems(null); } public Map> statsItems(String[] servers) { return stats(servers, "stats items\r\n", STATS); } public Map> statsSlabs() { return statsSlabs(null); } public Map> statsSlabs(String[] servers) { return stats(servers, "stats slabs\r\n", STATS); } public Map> statsCacheDump(int slabNumber, int limit) { return statsCacheDump(null, slabNumber, limit); } public Map> statsCacheDump(String[] servers, int slabNumber, int limit) { return stats(servers, String.format("stats cachedump %d %d\r\n", slabNumber, limit), ITEM); } private Map> stats(String[] servers, String command, String lineStart) { if (command == null || command.trim().equals("")) { if (log.isErrorEnabled()) log.error("++++ invalid / missing command for stats()"); return null; } // get all servers and iterate over them servers = (servers == null) ? pool.getServers() : servers; // if no servers, then return early if (servers == null || servers.length <= 0) { if (log.isErrorEnabled()) log.error("++++ no servers to check stats"); return null; } // array of stats Maps Map> statsMaps = new HashMap>(); for (int i = 0; i < servers.length; i++) { SchoonerSockIO sock = pool.getConnection(servers[i]); if (sock == null) { if (errorHandler != null) errorHandler.handleErrorOnStats(this, new IOException("no socket to server available")); continue; } // build command try { sock.write(command.getBytes()); // map to hold key value pairs Map stats = new HashMap(); // get result code SockInputStream input = new SockInputStream(sock, Integer.MAX_VALUE); String line; // loop over results while ((line = input.getLine()) != null) { if (line.startsWith(lineStart)) { String[] info = line.split(" ", 3); String key = info.length > 1 ? info[1] : null; String value = info.length > 2 ? info[2] : null; stats.put(key, value); } else if (END.equals(line)) { // finish when we get end from server break; } else if (line.startsWith(ERROR) || line.startsWith(CLIENT_ERROR) || line.startsWith(SERVER_ERROR)) { if (log.isErrorEnabled()) { log.error("++++ failed to query stats"); log.error("++++ server response: " + line); } break; } statsMaps.put(servers[i], stats); } } catch (Exception e) { // if we have an errorHandler, use its hook if (errorHandler != null) errorHandler.handleErrorOnStats(this, e); // exception thrown if (log.isErrorEnabled()) { log.error("++++ exception thrown while writing bytes to server on stats"); log.error(e.getMessage(), e); } try { sock.sockets.invalidateObject(sock); } catch (Exception e1) { if (log.isErrorEnabled()) log.error("++++ failed to close socket : " + sock.toString()); } sock = null; } finally { if (sock != null) { sock.close(); sock = null; } } } return statsMaps; } protected final class NIOLoader { protected Selector selector; protected int numConns = 0; protected AscIIClient mc; protected Connection[] conns; public NIOLoader(AscIIClient mc) { this.mc = mc; } private final class Connection { public List incoming = new ArrayList(); public ByteBuffer outgoing; public SchoonerSockIO sock; public SocketChannel channel; private boolean isDone = false; public Connection(SchoonerSockIO sock, StringBuilder request) throws IOException { this.sock = sock; outgoing = ByteBuffer.wrap(request.append("\r\n").toString().getBytes()); channel = (SocketChannel) sock.getChannel(); if (channel == null) throw new IOException("dead connection to: " + sock.getHost()); channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_WRITE, this); } public void close() { try { if (isDone) { channel.configureBlocking(true); sock.close(); return; } } catch (IOException e) { if (log.isErrorEnabled()) log.warn("++++ memcache: unexpected error closing normally"); } try { sock.sockets.invalidateObject(sock); } catch (Exception e1) { if (log.isErrorEnabled()) log.error("++++ failed to close socket : " + sock.toString()); } } public boolean isDone() { // if we know we're done, just say so if (isDone) return true; // else find out the hard way int strPos = B_END.length - 1; int bi = incoming.size() - 1; while (bi >= 0 && strPos >= 0) { ByteBuffer buf = incoming.get(bi); int pos = buf.position() - 1; while (pos >= 0 && strPos >= 0) { if (buf.get(pos--) != B_END[strPos--]) return false; } bi--; } isDone = strPos < 0; return isDone; } public ByteBuffer getBuffer() { int last = incoming.size() - 1; if (last >= 0 && incoming.get(last).hasRemaining()) { return incoming.get(last); } else { ByteBuffer newBuf = ByteBuffer.allocate(8192); incoming.add(newBuf); return newBuf; } } public String toString() { return new StringBuffer().append("Connection to ").append(sock.getHost()).append(" with ") .append(incoming.size()).append(" bufs; done is ").append(isDone).toString(); } } public void doMulti(boolean asString, Map sockKeys, String[] keys, Map ret) { long timeRemaining = 0; try { selector = Selector.open(); // get the sockets, flip them to non-blocking, and set up data // structures conns = new Connection[sockKeys.keySet().size()]; numConns = 0; for (Iterator i = sockKeys.keySet().iterator(); i.hasNext();) { // get SockIO obj from hostname String host = i.next(); SchoonerSockIO sock = pool.getConnection(host); if (sock == null) { if (errorHandler != null) errorHandler.handleErrorOnGet(this.mc, new IOException("no socket to server available"), keys); return; } conns[numConns++] = new Connection(sock, sockKeys.get(host)); } // the main select loop; ends when // 1) we've received data from all the servers, or // 2) we time out long startTime = System.currentTimeMillis(); long timeout = pool.getMaxBusy(); timeRemaining = timeout; while (numConns > 0 && timeRemaining > 0) { int n = selector.select(Math.min(timeout, 5000)); if (n > 0) { // we've got some activity; handle it Iterator it = selector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey key = it.next(); it.remove(); handleKey(key); } } else { // timeout likely... better check // TODO: This seems like a problem area that we need to // figure out how to handle. if (log.isErrorEnabled()) log.error("selector timed out waiting for activity"); } timeRemaining = timeout - (System.currentTimeMillis() - startTime); } } catch (IOException e) { return; } finally { if (log.isDebugEnabled()) log.debug("Disconnecting; numConns=" + numConns + " timeRemaining=" + timeRemaining); // run through our conns and either return them to the pool // or forcibly close them try { if (selector != null) selector.close(); } catch (IOException ignoreMe) { } for (Connection c : conns) { if (c != null) c.close(); } } // Done! Build the list of results and return them. If we get // here by a timeout, then some of the connections are probably // not done. But we'll return what we've got... for (Connection c : conns) { try { if (c.incoming.size() > 0 && c.isDone()) loadMulti(new ByteBufArrayInputStream(c.incoming), ret, asString); } catch (Exception e) { // shouldn't happen; we have all the data already if (log.isDebugEnabled()) log.debug("Caught the aforementioned exception on " + c); } } } public void doMulti(Map sockKeys, String[] keys, Map ret) { doMulti(false, sockKeys, keys, ret); } private void handleKey(SelectionKey key) throws IOException { if (key.isReadable()) readResponse(key); else if (key.isWritable()) writeRequest(key); } public void writeRequest(SelectionKey key) throws IOException { ByteBuffer buf = ((Connection) key.attachment()).outgoing; SocketChannel sc = (SocketChannel) key.channel(); if (buf.hasRemaining()) { sc.write(buf); } if (!buf.hasRemaining()) { key.interestOps(SelectionKey.OP_READ); } } public void readResponse(SelectionKey key) throws IOException { Connection conn = (Connection) key.attachment(); ByteBuffer buf = conn.getBuffer(); int count = conn.channel.read(buf); if (count > 0) { if (conn.isDone()) { key.cancel(); numConns--; return; } } } } public boolean sync(String key, Integer hashCode) { if (key == null) { if (log.isErrorEnabled()) log.error("null value for key passed to sync()"); return false; } // get SockIO obj from hash or from key SchoonerSockIO sock = pool.getSock(key, hashCode); // return false if unable to get SockIO obj if (sock == null) { return false; } // build command StringBuilder command = new StringBuilder("sync ").append(key); command.append("\r\n"); try { sock.write(command.toString().getBytes()); // if we get appropriate response back, then we return true // get result code String line = new SockInputStream(sock, Integer.MAX_VALUE).getLine(); if (SYNCED.equals(line)) { if (log.isInfoEnabled()) log.info(new StringBuffer().append("++++ sync of key: ").append(key) .append(" from cache was a success").toString()); // return sock to pool and bail here return true; } else if (NOTFOUND.equals(line)) { if (log.isInfoEnabled()) log.info(new StringBuffer().append("++++ sync of key: ").append(key) .append(" from cache failed as the key was not found").toString()); } else { if (log.isErrorEnabled()) { log.error(new StringBuffer().append("++++ error sync key: ").append(key).toString()); log.error(new StringBuffer().append("++++ server response: ").append(line).toString()); } } } catch (IOException e) { // exception thrown if (log.isErrorEnabled()) { log.error("++++ exception thrown while writing bytes to server on delete"); log.error(e.getMessage(), e); } try { sock.sockets.invalidateObject(sock); } catch (Exception e1) { if (log.isErrorEnabled()) log.error("++++ failed to close socket : " + sock.toString()); } sock = null; } finally { if (sock != null) { sock.close(); sock = null; } } return false; } public boolean sync(String key) { return sync(key, null); } public boolean syncAll() { return syncAll(null); } public boolean syncAll(String[] servers) { // get SockIOPool instance // return false if unable to get SockIO obj if (pool == null) { if (log.isErrorEnabled()) log.error("++++ unable to get SockIOPool instance"); return false; } // get all servers and iterate over them servers = (servers == null) ? pool.getServers() : servers; // if no servers, then return early if (servers == null || servers.length <= 0) { if (log.isErrorEnabled()) log.error("++++ no servers to sync"); return false; } boolean success = true; for (int i = 0; i < servers.length; i++) { SchoonerSockIO sock = pool.getConnection(servers[i]); if (sock == null) { if (log.isErrorEnabled()) log.error("++++ unable to get connection to : " + servers[i]); success = false; continue; } // build command String command = "sync_all\r\n"; try { sock.write(command.getBytes()); // if we get appropriate response back, then we return true // get result code String line = new SockInputStream(sock, Integer.MAX_VALUE).getLine(); success = (SYNCED.equals(line)) ? success && true : false; } catch (IOException e) { // exception thrown if (log.isErrorEnabled()) { log.error("++++ exception thrown while writing bytes to server on flushAll"); log.error(e.getMessage(), e); } try { sock.sockets.invalidateObject(sock); } catch (Exception e1) { if (log.isErrorEnabled()) log.error("++++ failed to close socket : " + sock.toString()); } success = false; sock = null; } finally { if (sock != null) { sock.close(); sock = null; } } } return success; } @Override public void setDefaultEncoding(String defaultEncoding) { this.defaultEncoding = defaultEncoding; } @Override public void setPrimitiveAsString(boolean primitiveAsString) { this.primitiveAsString = primitiveAsString; } @Override public void setSanitizeKeys(boolean sanitizeKeys) { this.sanitizeKeys = sanitizeKeys; } private String sanitizeKey(String key) throws UnsupportedEncodingException { return (sanitizeKeys) ? URLEncoder.encode(key, "UTF-8") : key; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy