/* * Copyright 2015 Couchbase, 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.couchbase.mock.memcached; import org.couchbase.mock.memcached.protocol.*; import org.couchbase.mock.subdoc.*; public class SubdocCommandExecutor implements CommandExecutor { public static class ResultInfo { private final Result result; private final ErrorCode status; ResultInfo(Result result, ErrorCode status) { this.result = result; this.status = status; } public String getNewDocString() { return result.getNewDocString(); } public String getMatchString() { return result.getMatchString(); } public ErrorCode getStatus() { return status; } } static ResultInfo executeSubdocOperation(Operation op, String doc, String path, String value, byte flags) { ErrorCode ec = ErrorCode.SUCCESS; Result result = null; boolean isMkdirP = (flags & (BinarySubdocCommand.PATHFLAG_MKDIR_P | BinarySubdocCommand.DOCFLAG_MKDOC)) != 0; try { result = Executor.execute(doc, path, op, value, isMkdirP); } catch (PathNotFoundException ex2) { ec = ErrorCode.SUBDOC_PATH_ENOENT; } catch (PathExistsException ex3) { ec = ErrorCode.SUBDOC_PATH_EEXISTS; } catch (BadNumberException ex4_1) { ec = ErrorCode.SUBDOC_DELTA_ERANGE; } catch (EmptyValueException ex4) { ec = ErrorCode.SUBDOC_VALUE_CANTINSERT; } catch (DocNotJsonException ex5) { ec = ErrorCode.SUBDOC_DOC_NOTJSON; } catch (InvalidPathException ex6) { ec = ErrorCode.SUBDOC_PATH_EINVAL; } catch (NumberTooBigException ex7) { ec = ErrorCode.SUBDOC_NUM_ERANGE; } catch (DeltaTooBigException ex8) { ec = ErrorCode.SUBDOC_DELTA_ERANGE; } catch (CannotInsertException ex9) { ec = ErrorCode.SUBDOC_VALUE_CANTINSERT; } catch (PathParseException ex10) { ec = ErrorCode.SUBDOC_PATH_EINVAL; } catch (PathMismatchException ex11) { ec = ErrorCode.SUBDOC_PATH_MISMATCH; } catch (ZeroDeltaException ex12) { ec = ErrorCode.SUBDOC_DELTA_ERANGE; } catch (SubdocException exFallback) { throw new RuntimeException(exFallback); } return new ResultInfo(result, ec); } public static ResultInfo executeSubdocLookup(Operation op, String doc, String path) { return executeSubdocOperation(op, doc, path, null, (byte)0); } @Override public void execute(BinaryCommand cmd, MemcachedServer server, MemcachedConnection client) { BinarySubdocCommand command = (BinarySubdocCommand)cmd; Operation subdocOp = command.getSubdocOp(); VBucketStore cache = server.getCache(cmd); SubdocItem subdocInput = command.getItem(); boolean isMkdoc = (command.getSubdocDocFlags() & BinarySubdocCommand.DOCFLAG_CREATEMASK) != 0; boolean isXattr = (command.getSubdocPathFlags() & BinarySubdocCommand.PATHFLAG_XATTR) != 0; boolean needsCreate = false; if (isMkdoc && !subdocOp.isCreative()) { client.sendResponse(new BinaryResponse(cmd, ErrorCode.EINVAL)); return; } Item existing = cache.get(subdocInput.getKeySpec()); if (existing == null) { if (!isMkdoc) { client.sendResponse(new BinaryResponse(cmd, ErrorCode.KEY_ENOENT)); return; } String newValue = Executor.getRootType(subdocInput.getPath(), subdocOp); if (newValue == null) { client.sendResponse(new BinaryResponse(cmd, ErrorCode.KEY_ENOENT)); return; } byte[] newBody, newAttr; if (isXattr) { newBody = null; newAttr = newValue.getBytes(); } else { newAttr = null; newBody = newValue.getBytes(); } existing = new Item(subdocInput.getKeySpec(), 0, 0, newBody, newAttr, 0); needsCreate = true; } else { if ((command.getSubdocDocFlags() & BinarySubdocCommand.DOCFLAG_ADD) != 0) { client.sendResponse(new BinaryResponse(command, ErrorCode.KEY_EEXISTS)); return; } } if (command.getCas() != 0) { subdocInput.setCas(command.getCas()); } else { subdocInput.setCas(existing.getCas()); } byte[] curValue; if (isXattr) { curValue = existing.getXattr(); if (curValue == null) { curValue = "{}".getBytes(); } } else { curValue = existing.getValue(); if (curValue == null) { curValue = "".getBytes(); } } ResultInfo rci = executeSubdocOperation(subdocOp, new String(curValue), subdocInput.getPath(), new String(subdocInput.getValue()), command.getSubdocPathFlags()); if (rci.getStatus() != ErrorCode.SUCCESS) { client.sendResponse(new BinaryResponse(cmd, rci.getStatus())); return; } byte[] value = null; if (subdocOp.returnsMatch()) { value = rci.getMatchString().getBytes(); } if (subdocOp.isMutator()) { MutationStatus ms; MutationInfoWriter miw = client.getMutinfoWriter(); byte[] xattr, body; if (isXattr) { xattr = rci.getNewDocString().getBytes(); body = existing.getValue(); } else { xattr = existing.getXattr(); body = rci.getNewDocString().getBytes(); } Item newItm = new Item( existing.getKeySpec(), existing.getFlags(), subdocInput.getExpiryTime(), body, xattr, subdocInput.getCas()); if (needsCreate) { ms = cache.add(newItm); if (ms.getStatus() == ErrorCode.KEY_EEXISTS) { execute(cmd, server, client); return; } } else { ms = cache.replace(newItm); } if (ms.getStatus() == ErrorCode.SUCCESS) { client.sendResponse(new BinaryResponse(cmd, ms, miw, newItm.getCas(), value)); } else { client.sendResponse(new BinaryResponse(cmd, ms.getStatus())); } } else { client.sendResponse(BinaryResponse.createWithValue(command, value, existing.getCas())); } } }