You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by kl...@apache.org on 2016/07/08 18:21:20 UTC

[12/21] incubator-geode git commit: GEODE-1566: rename GeodeRedisServer and repackage redis code into org.apache.geode

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/dfd481e0/geode-core/src/main/java/org/apache/geode/redis/internal/Command.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/Command.java b/geode-core/src/main/java/org/apache/geode/redis/internal/Command.java
new file mode 100755
index 0000000..25649d5
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/Command.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.geode.redis.internal;
+
+import io.netty.buffer.ByteBuf;
+
+import java.nio.channels.SocketChannel;
+import java.util.List;
+
+/**
+ * The command class is used in holding a received Redis command. Each sent 
+ * command resides in an instance of this class. This class is designed to be
+ * used strictly by getter and setter methods.
+ * 
+ *
+ */
+public class Command {
+
+  private final List<byte[]> commandElems;
+  private final RedisCommandType commandType;
+  private ByteBuf response;
+  private String key;
+  private ByteArrayWrapper bytes;
+
+  /**
+   * Constructor for {@link Command}. Must initialize Command with a {@link SocketChannel}
+   * and a {@link List} of command elements
+   * 
+   * @param commandElems List of elements in command
+   */
+  public Command (List<byte[]> commandElems) {
+    if (commandElems == null || commandElems.isEmpty())
+      throw new IllegalArgumentException("List of command elements cannot be empty -> List:" + commandElems);
+    this.commandElems = commandElems;
+    this.response = null;
+
+    RedisCommandType type;
+
+    try {
+      byte[] charCommand = commandElems.get(0);
+      String commandName = Coder.bytesToString(charCommand).toUpperCase();
+      type = RedisCommandType.valueOf(commandName);
+    } catch (Exception e) {
+      type = RedisCommandType.UNKNOWN;
+    }
+    this.commandType = type;
+
+  }
+
+  /**
+   * Used to get the command element list
+   * 
+   * @return List of command elements in form of {@link List}
+   */
+  public List<byte[]> getProcessedCommand() {
+    return this.commandElems;
+  }
+
+  /**
+   * Getter method for the command type
+   * 
+   * @return The command type
+   */
+  public RedisCommandType getCommandType() {
+    return this.commandType;
+  }
+
+  /**
+   * Getter method to get the response to be sent
+   * 
+   * @return The response
+   */
+  public ByteBuf getResponse() {
+    return response;
+  }
+
+  /**
+   * Setter method to set the response to be sent
+   * 
+   * @param response The response to be sent
+   */
+  public void setResponse(ByteBuf response) {
+    this.response = response;
+  }
+
+  public boolean hasError() {
+    if (response == null)
+      return false;
+
+    if (response.getByte(0) == Coder.ERROR_ID)
+      return true;
+
+    return false;
+  }
+
+  /**
+   * Convenience method to get a String representation of the key
+   * in a Redis command, always at the second position in the sent
+   * command array
+   * 
+   * @return Returns the second element in the parsed command
+   * list, which is always the key for commands indicating
+   * a key
+   */
+  public String getStringKey() {
+    if (this.commandElems.size() > 1) {
+      if (this.bytes == null) {
+        this.bytes = new ByteArrayWrapper(this.commandElems.get(1));
+        this.key = this.bytes.toString();
+      } else if (this.key == null)
+        this.key = this.bytes.toString();
+      return this.key;
+    } else 
+      return null;
+  }
+
+  public ByteArrayWrapper getKey() {
+    if (this.commandElems.size() > 1) {
+      if (this.bytes == null)
+        this.bytes = new ByteArrayWrapper(this.commandElems.get(1));
+      return this.bytes;
+    } else 
+      return null;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    for (byte[] bs : this.commandElems) {
+      b.append(Coder.bytesToString(bs));
+      b.append(' ');
+    }
+    return b.toString();
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/dfd481e0/geode-core/src/main/java/org/apache/geode/redis/internal/DoubleWrapper.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/DoubleWrapper.java b/geode-core/src/main/java/org/apache/geode/redis/internal/DoubleWrapper.java
new file mode 100755
index 0000000..3e362c3
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/DoubleWrapper.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.geode.redis.internal;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import com.gemstone.gemfire.DataSerializable;
+import com.gemstone.gemfire.DataSerializer;
+
+/**
+ * This is a wrapper class for doubles, similar to {@link ByteArrayWrapper}
+ * 
+ *
+ */
+public class DoubleWrapper implements DataSerializable, Comparable<Object> {
+
+  private static final long serialVersionUID = 6946858357297398633L;
+
+  public Double score;
+  private String toString;
+  
+  public DoubleWrapper() {}
+
+  public DoubleWrapper(Double dubs) {
+    this.score = dubs;
+  }
+
+  @Override
+  public void toData(DataOutput out) throws IOException {
+    DataSerializer.writeDouble(score, out);
+  }
+
+  @Override
+  public void fromData(DataInput in) throws IOException, ClassNotFoundException {
+    this.score = DataSerializer.readDouble(in);
+  }
+
+  @Override
+  public int compareTo(Object arg0) {
+    Double other;
+    if (arg0 instanceof DoubleWrapper) {
+      other = ((DoubleWrapper) arg0).score;
+    } else if (arg0 instanceof Double) {
+      other = (Double) arg0;
+    } else
+      return 0;
+    Double diff = this.score - other;
+    if (diff > 0)
+      return 1;
+    else if (diff < 0)
+      return -1;
+    else
+      return 0;
+  }
+
+  public String toString() {
+    if (this.toString == null)
+      this.toString = Coder.doubleToString(score);
+    return this.toString;
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/dfd481e0/geode-core/src/main/java/org/apache/geode/redis/internal/ExecutionHandlerContext.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/ExecutionHandlerContext.java b/geode-core/src/main/java/org/apache/geode/redis/internal/ExecutionHandlerContext.java
new file mode 100644
index 0000000..516ccaf
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/ExecutionHandlerContext.java
@@ -0,0 +1,379 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.geode.redis.internal;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.codec.DecoderException;
+import io.netty.util.concurrent.EventExecutor;
+
+import java.io.IOException;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.gemstone.gemfire.LogWriter;
+import com.gemstone.gemfire.cache.Cache;
+import com.gemstone.gemfire.cache.CacheClosedException;
+import com.gemstone.gemfire.cache.CacheTransactionManager;
+import com.gemstone.gemfire.cache.RegionDestroyedException;
+import com.gemstone.gemfire.cache.TransactionException;
+import com.gemstone.gemfire.cache.TransactionId;
+import com.gemstone.gemfire.cache.UnsupportedOperationInTransactionException;
+import com.gemstone.gemfire.cache.query.QueryInvocationTargetException;
+import com.gemstone.gemfire.cache.query.RegionNotFoundException;
+import org.apache.geode.redis.internal.executor.transactions.TransactionExecutor;
+import org.apache.geode.redis.GeodeRedisServer;
+
+/**
+ * This class extends {@link ChannelInboundHandlerAdapter} from Netty and it is
+ * the last part of the channel pipeline. The {@link ByteToCommandDecoder} forwards a
+ * {@link Command} to this class which executes it and sends the result back to the
+ * client. Additionally, all exception handling is done by this class. 
+ * <p>
+ * Besides being part of Netty's pipeline, this class also serves as a context to the
+ * execution of a command. It abstracts transactions, provides access to the {@link RegionProvider}
+ * and anything else an executing {@link Command} may need.
+ * 
+ *
+ */
+public class ExecutionHandlerContext extends ChannelInboundHandlerAdapter {
+
+  private static final int WAIT_REGION_DSTRYD_MILLIS = 100;
+  private static final int MAXIMUM_NUM_RETRIES = (1000*60)/WAIT_REGION_DSTRYD_MILLIS; // 60 seconds total
+
+  private final Cache cache;
+  private final GeodeRedisServer server;
+  private final LogWriter logger;
+  private final Channel channel;
+  private final AtomicBoolean needChannelFlush;
+  private final Runnable flusher;
+  private final EventExecutor lastExecutor;
+  private final ByteBufAllocator byteBufAllocator;
+  /**
+   * TransactionId for any transactions started by this client
+   */
+  private TransactionId transactionID;
+
+  /**
+   * Queue of commands for a given transaction
+   */
+  private Queue<Command> transactionQueue;
+  private final RegionProvider regionProvider;
+  private final byte[] authPwd;
+
+  private boolean isAuthenticated;
+
+  /**
+   * Default constructor for execution contexts. 
+   * 
+   * @param ch Channel used by this context, should be one to one
+   * @param cache The Geode cache instance of this vm
+   * @param regionProvider The region provider of this context
+   * @param server Instance of the server it is attached to, only used so that any execution can initiate a shutdwon
+   * @param pwd Authentication password for each context, can be null
+   */
+  public ExecutionHandlerContext(Channel ch, Cache cache, RegionProvider regionProvider, GeodeRedisServer server, byte[] pwd) {
+    if (ch == null || cache == null || regionProvider == null || server == null)
+      throw new IllegalArgumentException("Only the authentication password may be null");
+    this.cache = cache;
+    this.server = server;
+    this.logger = cache.getLogger();
+    this.channel = ch;
+    this.needChannelFlush  = new AtomicBoolean(false);
+    this.flusher = new Runnable() {
+
+      @Override
+      public void run() {
+        flushChannel();
+      }
+
+    };
+    this.lastExecutor = channel.pipeline().lastContext().executor();
+    this.byteBufAllocator = channel.alloc();
+    this.transactionID = null;
+    this.transactionQueue = null; // Lazy
+    this.regionProvider = regionProvider;
+    this.authPwd = pwd;
+    this.isAuthenticated = pwd != null ? false : true;
+  }
+
+  private void flushChannel() {
+    while (needChannelFlush.getAndSet(false)) {
+      channel.flush();
+    }
+  }
+
+  private void writeToChannel(ByteBuf message) {
+    channel.write(message, channel.voidPromise());
+    if (!needChannelFlush.getAndSet(true)) {
+      this.lastExecutor.execute(flusher);
+    }
+  }
+
+  /**
+   * This will handle the execution of received commands
+   */
+  @Override
+  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+    Command command = (Command) msg;
+    executeCommand(ctx, command);
+  }
+
+  /**
+   * Exception handler for the entire pipeline
+   */
+  @Override
+  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+    if (cause instanceof IOException) {
+      channelInactive(ctx);
+      return;
+    }
+    ByteBuf response = getExceptionResponse(ctx, cause);
+    writeToChannel(response);
+  }
+
+  private ByteBuf getExceptionResponse(ChannelHandlerContext ctx, Throwable cause) {
+    ByteBuf response; 
+    if (cause instanceof RedisDataTypeMismatchException)
+      response = Coder.getWrongTypeResponse(this.byteBufAllocator, cause.getMessage());
+    else if (cause instanceof DecoderException && cause.getCause() instanceof RedisCommandParserException)
+      response = Coder.getErrorResponse(this.byteBufAllocator, RedisConstants.PARSING_EXCEPTION_MESSAGE);
+    else if (cause instanceof RegionCreationException) {
+      this.logger.error(cause);
+      response = Coder.getErrorResponse(this.byteBufAllocator, RedisConstants.ERROR_REGION_CREATION);
+    } else if (cause instanceof InterruptedException || cause instanceof CacheClosedException)
+      response = Coder.getErrorResponse(this.byteBufAllocator, RedisConstants.SERVER_ERROR_SHUTDOWN);
+    else if (cause instanceof IllegalStateException) {
+      response = Coder.getErrorResponse(this.byteBufAllocator, cause.getMessage());
+    } else {
+      if (this.logger.errorEnabled())
+        this.logger.error("GeodeRedisServer-Unexpected error handler for " + ctx.channel(), cause);
+      response = Coder.getErrorResponse(this.byteBufAllocator, RedisConstants.SERVER_ERROR_MESSAGE);
+    }
+    return response;
+  }
+
+  @Override
+  public void channelInactive(ChannelHandlerContext ctx) {
+    if (logger.fineEnabled())
+      logger.fine("GeodeRedisServer-Connection closing with " + ctx.channel().remoteAddress());
+    ctx.channel().close();
+    ctx.close();
+  }
+
+  private void executeCommand(ChannelHandlerContext ctx, Command command) throws Exception {
+    RedisCommandType type = command.getCommandType();
+    Executor exec = type.getExecutor();
+    if (isAuthenticated) {
+      if (type == RedisCommandType.SHUTDOWN) {
+        this.server.shutdown();
+        return;
+      }
+      if (hasTransaction() && !(exec instanceof TransactionExecutor))
+        executeWithTransaction(ctx, exec, command);
+      else
+        executeWithoutTransaction(exec, command); 
+
+      if (hasTransaction() && command.getCommandType() != RedisCommandType.MULTI) {
+        writeToChannel(Coder.getSimpleStringResponse(this.byteBufAllocator, RedisConstants.COMMAND_QUEUED));
+      } else {
+        ByteBuf response = command.getResponse();
+        writeToChannel(response);
+      }
+    } else if (type == RedisCommandType.QUIT) {
+      exec.executeCommand(command, this);
+      ByteBuf response = command.getResponse();
+      writeToChannel(response);
+      channelInactive(ctx);
+    } else if (type == RedisCommandType.AUTH) {
+      exec.executeCommand(command, this);
+      ByteBuf response = command.getResponse();
+      writeToChannel(response);
+    } else {
+      ByteBuf r = Coder.getNoAuthResponse(this.byteBufAllocator, RedisConstants.ERROR_NOT_AUTH);
+      writeToChannel(r);
+    }
+  }
+
+  /**
+   * Private helper method to execute a command without a transaction, done for
+   * special exception handling neatness
+   * 
+   * @param exec Executor to use
+   * @param command Command to execute
+   * @throws Exception Throws exception if exception is from within execution and not to be handled
+   */
+  private void executeWithoutTransaction(final Executor exec, Command command) throws Exception {
+    Exception cause = null;
+    for (int i = 0; i < MAXIMUM_NUM_RETRIES; i++) {
+      try {
+        exec.executeCommand(command, this);
+        return;
+      } catch (Exception e) {
+        cause = e;
+        if (e instanceof RegionDestroyedException || e instanceof RegionNotFoundException || e.getCause() instanceof QueryInvocationTargetException)
+          Thread.sleep(WAIT_REGION_DSTRYD_MILLIS);
+      }
+    }
+    throw cause;
+  }
+
+  private void executeWithTransaction(ChannelHandlerContext ctx, final Executor exec, Command command) throws Exception {
+    CacheTransactionManager txm = cache.getCacheTransactionManager();
+    TransactionId transactionId = getTransactionID();
+    txm.resume(transactionId);
+    try {
+      exec.executeCommand(command, this);
+    } catch(UnsupportedOperationInTransactionException e) {
+      command.setResponse(Coder.getErrorResponse(this.byteBufAllocator, RedisConstants.ERROR_UNSUPPORTED_OPERATION_IN_TRANSACTION));
+    } catch (TransactionException e) {
+      command.setResponse(Coder.getErrorResponse(this.byteBufAllocator, RedisConstants.ERROR_TRANSACTION_EXCEPTION));
+    } catch (Exception e) {
+      ByteBuf response = getExceptionResponse(ctx, e);
+      command.setResponse(response);
+    }
+    getTransactionQueue().add(command);
+    transactionId = txm.suspend();
+    setTransactionID(transactionId);
+  }
+
+  /**
+   * Get the current transacationId
+   * 
+   * @return The current transactionId, null if one doesn't exist
+   */
+  public TransactionId getTransactionID() {
+    return this.transactionID;
+  }
+
+  /**
+   * Check if client has transaction
+   * 
+   * @return True if client has transaction, false otherwise
+   */
+  public boolean hasTransaction() {
+    return transactionID != null;
+  }
+
+  /**
+   * Setter method for transaction
+   * 
+   * @param id TransactionId of current transaction for client
+   */
+  public void setTransactionID(TransactionId id) {
+    this.transactionID = id;
+  }
+
+  /**
+   * Reset the transaction of client
+   */
+  public void clearTransaction() {
+    this.transactionID = null;
+    if (this.transactionQueue != null) {
+      for (Command c : this.transactionQueue) {
+        ByteBuf r = c.getResponse();
+        if (r != null)
+          r.release();
+      }
+      this.transactionQueue.clear();
+    }
+  }
+
+  /**
+   * Getter for transaction command queue
+   * 
+   * @return Command queue
+   */
+  public Queue<Command> getTransactionQueue() {
+    if (this.transactionQueue == null)
+      this.transactionQueue = new ConcurrentLinkedQueue<Command>();
+    return this.transactionQueue;
+  }
+
+  /**
+   * {@link ByteBuf} allocator for this context. All executors
+   * must use this pooled allocator as opposed to having unpooled buffers
+   * for maximum performance
+   * 
+   * @return allocator instance
+   */
+  public ByteBufAllocator getByteBufAllocator() {
+    return this.byteBufAllocator;
+  }
+
+  /**
+   * Gets the provider of Regions
+   * 
+   * @return Provider
+   */
+  public RegionProvider getRegionProvider() {
+    return this.regionProvider;
+  }
+
+  /**
+   * Getter for manager to allow pausing and resuming transactions
+   * @return Instance
+   */
+  public CacheTransactionManager getCacheTransactionManager() {
+    return this.cache.getCacheTransactionManager();
+  }
+
+  /**
+   * Getter for logger
+   * @return instance
+   */
+  public LogWriter getLogger() {
+    return this.cache.getLogger();
+  }
+
+  /**
+   * Get the channel for this context
+   * @return instance
+   *
+  public Channel getChannel() {
+    return this.channel;
+  }
+   */
+
+  /**
+   * Get the authentication password, this will be same server wide.
+   * It is exposed here as opposed to {@link GeodeRedisServer}.
+   * @return password
+   */
+  public byte[] getAuthPwd() {
+    return this.authPwd;
+  }
+
+  /**
+   * Checker if user has authenticated themselves
+   * @return True if no authentication required or authentication complete, false otherwise
+   */
+  public boolean isAuthenticated() {
+    return this.isAuthenticated;
+  }
+
+  /**
+   * Lets this context know the authentication is complete
+   */
+  public void setAuthenticationVerified() {
+    this.isAuthenticated = true;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/dfd481e0/geode-core/src/main/java/org/apache/geode/redis/internal/Executor.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/Executor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/Executor.java
new file mode 100755
index 0000000..c72a2e4
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/Executor.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.geode.redis.internal;
+
+
+/**
+ * Interface for executors of a {@link Command}
+ * 
+ *
+ */
+public interface Executor {
+
+  /**
+   * This method executes the command and sets the response. Any runtime errors
+   * from this execution should be handled by caller to ensure the client gets 
+   * a response
+   * 
+   * @param command The command to be executed
+   * @param context The execution context by which this command is to be executed
+   */
+  public void executeCommand(Command command, ExecutionHandlerContext context);
+  
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/dfd481e0/geode-core/src/main/java/org/apache/geode/redis/internal/Extendable.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/Extendable.java b/geode-core/src/main/java/org/apache/geode/redis/internal/Extendable.java
new file mode 100644
index 0000000..e5c92f0
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/Extendable.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.geode.redis.internal;
+
+/**
+ * This defines a command that can be extended, and there may need some level of abstraction
+ * 
+ *
+ */
+public interface Extendable {
+
+  /**
+   * Getter for error message in case of argument arity mismatch
+   * @return Error string
+   */
+  public String getArgsError();
+  
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/dfd481e0/geode-core/src/main/java/org/apache/geode/redis/internal/RedisCommandParserException.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/RedisCommandParserException.java b/geode-core/src/main/java/org/apache/geode/redis/internal/RedisCommandParserException.java
new file mode 100755
index 0000000..5bb89dd
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/RedisCommandParserException.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.geode.redis.internal;
+
+/**
+ * Exception thrown by CommandParser (non-existent class) when a command has illegal syntax
+ * 
+ *
+ */
+public class RedisCommandParserException extends Exception {
+
+  private static final long serialVersionUID = 4707944288714910949L;
+
+  public RedisCommandParserException() {
+    super();
+  }
+
+  public RedisCommandParserException(String message) {
+    super(message);
+  }
+
+  public RedisCommandParserException(Throwable cause) {
+    super(cause);
+  }
+
+  public RedisCommandParserException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+}