/** * Copyright 2008 ThimbleWare Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.openspaces.memcached.protocol; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.netty.channel.*; import org.jboss.netty.channel.group.DefaultChannelGroup; import org.openspaces.memcached.*; import org.openspaces.memcached.protocol.exceptions.UnknownCommandException; import java.util.concurrent.atomic.AtomicInteger; // TODO implement flush_all delay /** * The actual command handler, which is responsible for processing the CommandMessage instances * that are inbound from the protocol decoders. * <p/> * One instance is shared among the entire pipeline, since this handler is stateless, apart from some globals * for the entire daemon. * <p/> * The command handler produces ResponseMessages which are destined for the response encoder. */ @ChannelHandler.Sharable public final class MemcachedCommandHandler extends SimpleChannelUpstreamHandler { protected final static Log logger = LogFactory.getLog(MemcachedCommandHandler.class); public final AtomicInteger curr_conns = new AtomicInteger(); public final AtomicInteger total_conns = new AtomicInteger(); /** * The following state variables are universal for the entire daemon. These are used for statistics gathering. * In order for these values to work properly, the handler _must_ be declared with a ChannelPipelineCoverage * of "all". */ public final String version; public final int idle_limit; public final boolean verbose; /** * The actual physical data storage. */ private final SpaceCache cache; /** * The channel group for the entire daemon, used for handling global cleanup on shutdown. */ private final DefaultChannelGroup channelGroup; /** * Construct the server session handler * * @param cache the cache to use * @param memcachedVersion the version string to return to clients * @param verbosity verbosity level for debugging * @param idle how long sessions can be idle for * @param channelGroup */ public MemcachedCommandHandler(SpaceCache cache, String memcachedVersion, boolean verbosity, int idle, DefaultChannelGroup channelGroup) { this.cache = cache; version = memcachedVersion; verbose = verbosity; idle_limit = idle; this.channelGroup = channelGroup; } /** * On open we manage some statistics, and add this connection to the channel group. * * @param channelHandlerContext * @param channelStateEvent * @throws Exception */ @Override public void channelOpen(ChannelHandlerContext channelHandlerContext, ChannelStateEvent channelStateEvent) throws Exception { total_conns.incrementAndGet(); curr_conns.incrementAndGet(); channelGroup.add(channelHandlerContext.getChannel()); } /** * On close we manage some statistics, and remove this connection from the channel group. * * @param channelHandlerContext * @param channelStateEvent * @throws Exception */ @Override public void channelClosed(ChannelHandlerContext channelHandlerContext, ChannelStateEvent channelStateEvent) throws Exception { curr_conns.decrementAndGet(); channelGroup.remove(channelHandlerContext.getChannel()); } /** * The actual meat of the matter. Turn CommandMessages into executions against the physical cache, and then * pass on the downstream messages. * * @param channelHandlerContext * @param messageEvent * @throws Exception */ @Override public void messageReceived(ChannelHandlerContext channelHandlerContext, MessageEvent messageEvent) throws Exception { if (!(messageEvent.getMessage() instanceof CommandMessage)) { // Ignore what this encoder can't encode. channelHandlerContext.sendUpstream(messageEvent); return; } CommandMessage command = (CommandMessage) messageEvent.getMessage(); Op cmd = command.op; int cmdKeysSize = command.keys.size(); // first process any messages in the delete queue cache.asyncEventPing(); // now do the real work if (this.verbose) { StringBuilder log = new StringBuilder(); log.append(cmd); if (command.element != null) { log.append(" ").append(command.element.getKey()); } for (int i = 0; i < cmdKeysSize; i++) { log.append(" ").append(command.keys.get(i)); } logger.info(log.toString()); } Channel channel = messageEvent.getChannel(); if (cmd == Op.GET || cmd == Op.GETS) { handleGets(channelHandlerContext, command, channel); } else if (cmd == Op.SET) { handleSet(channelHandlerContext, command, channel); } else if (cmd == Op.CAS) { handleCas(channelHandlerContext, command, channel); } else if (cmd == Op.ADD) { handleAdd(channelHandlerContext, command, channel); } else if (cmd == Op.REPLACE) { handleReplace(channelHandlerContext, command, channel); } else if (cmd == Op.APPEND) { handleAppend(channelHandlerContext, command, channel); } else if (cmd == Op.PREPEND) { handlePrepend(channelHandlerContext, command, channel); } else if (cmd == Op.INCR) { handleIncr(channelHandlerContext, command, channel); } else if (cmd == Op.DECR) { handleDecr(channelHandlerContext, command, channel); } else if (cmd == Op.DELETE) { handleDelete(channelHandlerContext, command, channel); } else if (cmd == Op.STATS) { handleStats(channelHandlerContext, command, cmdKeysSize, channel); } else if (cmd == Op.VERSION) { handleVersion(channelHandlerContext, command, channel); } else if (cmd == Op.QUIT) { handleQuit(channel); } else if (cmd == Op.FLUSH_ALL) { handleFlush(channelHandlerContext, command, channel); } else if (cmd == Op.VERBOSITY) { handleVerbosity(channelHandlerContext, command, channel); } else if (cmd == null) { // NOOP handleNoOp(channelHandlerContext, command); } else { throw new UnknownCommandException("unknown command:" + cmd); } } protected void handleNoOp(ChannelHandlerContext channelHandlerContext, CommandMessage command) { Channels.fireMessageReceived(channelHandlerContext, new ResponseMessage(command)); } protected void handleFlush(ChannelHandlerContext channelHandlerContext, CommandMessage command, Channel channel) { Channels.fireMessageReceived(channelHandlerContext, new ResponseMessage(command).withFlushResponse(cache.flush_all(command.time)), channel.getRemoteAddress()); } protected void handleVerbosity(ChannelHandlerContext channelHandlerContext, CommandMessage command, Channel channel) { Channels.fireMessageReceived(channelHandlerContext, new ResponseMessage(command), channel.getRemoteAddress()); } protected void handleQuit(Channel channel) { channel.disconnect(); } protected void handleVersion(ChannelHandlerContext channelHandlerContext, CommandMessage command, Channel channel) { ResponseMessage responseMessage = new ResponseMessage(command); responseMessage.version = version; Channels.fireMessageReceived(channelHandlerContext, responseMessage, channel.getRemoteAddress()); } protected void handleStats(ChannelHandlerContext channelHandlerContext, CommandMessage command, int cmdKeysSize, Channel channel) { String option = ""; if (cmdKeysSize > 0) { option = new String(command.keys.get(0).bytes); } Channels.fireMessageReceived(channelHandlerContext, new ResponseMessage(command).withStatResponse(cache.stat(option)), channel.getRemoteAddress()); } protected void handleDelete(ChannelHandlerContext channelHandlerContext, CommandMessage command, Channel channel) { SpaceCache.DeleteResponse dr = cache.delete(command.keys.get(0), command.time); Channels.fireMessageReceived(channelHandlerContext, new ResponseMessage(command).withDeleteResponse(dr), channel.getRemoteAddress()); } protected void handleDecr(ChannelHandlerContext channelHandlerContext, CommandMessage command, Channel channel) { Integer incrDecrResp = cache.get_add(command.keys.get(0), -1 * command.incrAmount); Channels.fireMessageReceived(channelHandlerContext, new ResponseMessage(command).withIncrDecrResponse(incrDecrResp), channel.getRemoteAddress()); } protected void handleIncr(ChannelHandlerContext channelHandlerContext, CommandMessage command, Channel channel) { Integer incrDecrResp = cache.get_add(command.keys.get(0), command.incrAmount); // TODO support default value and expiry!! Channels.fireMessageReceived(channelHandlerContext, new ResponseMessage(command).withIncrDecrResponse(incrDecrResp), channel.getRemoteAddress()); } protected void handlePrepend(ChannelHandlerContext channelHandlerContext, CommandMessage command, Channel channel) { SpaceCache.StoreResponse ret; ret = cache.prepend(command.element); Channels.fireMessageReceived(channelHandlerContext, new ResponseMessage(command).withResponse(ret), channel.getRemoteAddress()); } protected void handleAppend(ChannelHandlerContext channelHandlerContext, CommandMessage command, Channel channel) { SpaceCache.StoreResponse ret; ret = cache.append(command.element); Channels.fireMessageReceived(channelHandlerContext, new ResponseMessage(command).withResponse(ret), channel.getRemoteAddress()); } protected void handleReplace(ChannelHandlerContext channelHandlerContext, CommandMessage command, Channel channel) { SpaceCache.StoreResponse ret; ret = cache.replace(command.element); Channels.fireMessageReceived(channelHandlerContext, new ResponseMessage(command).withResponse(ret), channel.getRemoteAddress()); } protected void handleAdd(ChannelHandlerContext channelHandlerContext, CommandMessage command, Channel channel) { SpaceCache.StoreResponse ret; ret = cache.add(command.element); Channels.fireMessageReceived(channelHandlerContext, new ResponseMessage(command).withResponse(ret), channel.getRemoteAddress()); } protected void handleCas(ChannelHandlerContext channelHandlerContext, CommandMessage command, Channel channel) { SpaceCache.StoreResponse ret; ret = cache.cas(command.cas_key, command.element); Channels.fireMessageReceived(channelHandlerContext, new ResponseMessage(command).withResponse(ret), channel.getRemoteAddress()); } protected void handleSet(ChannelHandlerContext channelHandlerContext, CommandMessage command, Channel channel) { SpaceCache.StoreResponse ret; ret = cache.set(command.element); Channels.fireMessageReceived(channelHandlerContext, new ResponseMessage(command).withResponse(ret), channel.getRemoteAddress()); } protected void handleGets(ChannelHandlerContext channelHandlerContext, CommandMessage command, Channel channel) { Key[] keys = new Key[command.keys.size()]; keys = command.keys.toArray(keys); LocalCacheElement[] results = get(keys); ResponseMessage resp = new ResponseMessage(command).withElements(results); Channels.fireMessageReceived(channelHandlerContext, resp, channel.getRemoteAddress()); } /** * Get an element from the cache * * @param keys the key for the element to lookup * @return the element, or 'null' in case of cache miss. */ private LocalCacheElement[] get(Key... keys) { return cache.get(keys); } }