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);
+ }
+
+}