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

net.spy.memcached.protocol.binary.OperationImpl Maven / Gradle / Ivy

The newest version!
/*
 * arcus-java-client : Arcus Java client
 * Copyright 2010-2014 NAVER Corp.
 * Copyright 2014-2021 JaM2in Co., Ltd.
 *
 * 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 net.spy.memcached.protocol.binary;

import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.concurrent.atomic.AtomicInteger;

import net.spy.memcached.CASResponse;
import net.spy.memcached.KeyUtil;
import net.spy.memcached.ops.CASOperationStatus;
import net.spy.memcached.ops.OperationCallback;
import net.spy.memcached.ops.OperationErrorType;
import net.spy.memcached.ops.OperationState;
import net.spy.memcached.ops.OperationStatus;
import net.spy.memcached.ops.StatusCode;
import net.spy.memcached.protocol.BaseOperationImpl;

/**
 * Base class for binary operations.
 */
abstract class OperationImpl extends BaseOperationImpl {

  protected static final byte REQ_MAGIC = (byte) 0x80;
  protected static final byte RES_MAGIC = (byte) 0x81;
  protected static final int MIN_RECV_PACKET = 24;

  /**
   * Error code for items that were not found.
   */
  protected static final int ERR_NOT_FOUND = 1;
  protected static final int ERR_EXISTS = 2;
  protected static final int ERR_EINVAL = 4;
  protected static final int ERR_NOT_STORED = 5;

  protected static final OperationStatus NOT_FOUND_STATUS =
          new CASOperationStatus(false, "Not Found",
                  CASResponse.NOT_FOUND, StatusCode.ERR_NOT_FOUND);
  protected static final OperationStatus EXISTS_STATUS =
          new CASOperationStatus(false, "Object exists",
                  CASResponse.EXISTS, StatusCode.ERR_EXISTS);
  protected static final OperationStatus NOT_STORED_STATUS =
          new CASOperationStatus(false, "Not Stored",
                  CASResponse.NOT_FOUND, StatusCode.ERR_NOT_STORED);

  protected static final byte[] EMPTY_BYTES = new byte[0];

  protected static final OperationStatus STATUS_OK =
          new CASOperationStatus(true, "OK",
                  CASResponse.OK, StatusCode.SUCCESS);

  private static final AtomicInteger seqNumber = new AtomicInteger(0);

  // request header fields
  private final int cmd;
  protected final int opaque;

  private final byte[] header = new byte[MIN_RECV_PACKET];
  private int headerOffset = 0;
  private byte[] payload = null;

  // Response header fields
  protected int keyLen;
  protected int responseCmd;
  protected int errorCode;
  protected int responseOpaque;
  protected long responseCas;

  private int payloadOffset = 0;

  /**
   * Construct with opaque.
   *
   * @param o  the opaque value.
   * @param cb
   */
  protected OperationImpl(int c, int o, OperationCallback cb) {
    super();
    cmd = c;
    opaque = o;
    setCallback(cb);
  }

  protected void resetInput() {
    payload = null;
    payloadOffset = 0;
    headerOffset = 0;
  }

  // Base response packet format:
  //    0      1       2  3    4         5         6  7    8 9 10 11
  //    # magic, opcode, keylen, extralen, datatype, status, bodylen,
  //    12,3,4,5  16
  //    opaque, cas
  //    RES_PKT_FMT=">BBHBBHIIQ"

  @Override
  public void readFromBuffer(ByteBuffer b) throws IOException {
    // First process headers if we haven't completed them yet
    if (headerOffset < MIN_RECV_PACKET) {
      int toRead = MIN_RECV_PACKET - headerOffset;
      int available = b.remaining();
      toRead = Math.min(toRead, available);
      getLogger().debug("Reading %d header bytes", toRead);
      b.get(header, headerOffset, toRead);
      headerOffset += toRead;

      // We've completed reading the header.  Prepare body read.
      if (headerOffset == MIN_RECV_PACKET) {
        int magic = header[0];
        assert magic == RES_MAGIC : "Invalid magic:  " + magic;
        responseCmd = header[1];
        assert cmd == -1 || responseCmd == cmd
                : "Unexpected response command value";
        keyLen = decodeShort(header, 2);
        // TODO:  Examine extralen and datatype
        errorCode = decodeShort(header, 6);
        int bytesToRead = decodeInt(header, 8);
        payload = new byte[bytesToRead];
        responseOpaque = decodeInt(header, 12);
        responseCas = decodeLong(header, 16);
        assert opaqueIsValid() : "Opaque is not valid";
      }
    }

    // Now process the payload if we can.
    if (headerOffset >= MIN_RECV_PACKET && payload == null) {
      finishedPayload(EMPTY_BYTES);
    } else if (payload != null) {
      int toRead = payload.length - payloadOffset;
      int available = b.remaining();
      toRead = Math.min(toRead, available);
      getLogger().debug("Reading %d payload bytes", toRead);
      b.get(payload, payloadOffset, toRead);
      payloadOffset += toRead;

      // Have we read it all?
      if (payloadOffset == payload.length) {
        finishedPayload(payload);
      }
    } else {
      // Haven't read enough to make up a payload.  Must read more.
      getLogger().debug("Only read %d of the %d needed to fill a header",
              headerOffset, MIN_RECV_PACKET);
    }

  }

  protected void finishedPayload(byte[] pl) throws IOException {
    if (errorCode != 0) {
      OperationStatus status = getStatusForErrorCode(errorCode, pl);
      if (status == null) {
        handleError(OperationErrorType.SERVER, new String(pl));
      } else {
        getCallback().receivedStatus(status);
        transitionState(OperationState.COMPLETE);
      }
    } else {
      decodePayload(pl);
      transitionState(OperationState.COMPLETE);
    }
  }

  /**
   * Get the OperationStatus object for the given error code.
   *
   * @param errCode the error code
   * @return the status to return, or null if this is an exceptional case
   */
  protected OperationStatus getStatusForErrorCode(int errCode, byte[] errPl) {
    return null;
  }

  /**
   * Decode the given payload for this command.
   *
   * @param pl the payload.
   */
  protected void decodePayload(byte[] pl) {
    assert pl.length == 0 : "Payload has bytes, but decode isn't overridden";
    getCallback().receivedStatus(STATUS_OK);
  }

  /**
   * Validate an opaque value from the header.
   * This may be overridden from a subclass where the opaque isn't expected
   * to always be the same as the request opaque.
   */
  protected boolean opaqueIsValid() {
    if (responseOpaque != opaque) {
      getLogger().warn("Expected opaque:  %d, got opaque:  %d\n",
              responseOpaque, opaque);
    }
    return responseOpaque == opaque;
  }

  static int decodeShort(byte[] data, int i) {
    return (data[i] & 0xff) << 8
            | (data[i + 1] & 0xff);
  }

  static int decodeInt(byte[] data, int i) {
    return (data[i] & 0xff) << 24
            | (data[i + 1] & 0xff) << 16
            | (data[i + 2] & 0xff) << 8
            | (data[i + 3] & 0xff);
  }

  static long decodeUnsignedInt(byte[] data, int i) {
    return ((long) (data[i] & 0xff) << 24)
            | ((data[i + 1] & 0xff) << 16)
            | ((data[i + 2] & 0xff) << 8)
            | (data[i + 3] & 0xff);
  }

  static long decodeLong(byte[] data, int i) {
    return (data[i] & 0xff) << 56
            | (data[i + 1] & 0xff) << 48
            | (data[i + 2] & 0xff) << 40
            | (data[i + 3] & 0xff) << 32
            | (data[i + 4] & 0xff) << 24
            | (data[i + 5] & 0xff) << 16
            | (data[i + 6] & 0xff) << 8
            | (data[i + 7] & 0xff);
  }

  /**
   * Prepare a send buffer.
   *
   * @param key          the key (for keyed ops)
   * @param cas          the cas value
   * @param val          the data payload
   * @param extraHeaders any additional headers that need to be sent
   */
  protected void prepareBuffer(String key, long cas, byte[] val,
                               Object... extraHeaders) {
    int extraLen = 0;
    for (Object o : extraHeaders) {
      if (o instanceof Integer) {
        extraLen += 4;
      } else if (o instanceof byte[]) {
        extraLen += ((byte[]) o).length;
      } else if (o instanceof Long) {
        extraLen += 8;
      } else {
        assert false : "Unhandled extra header type:  " + o.getClass();
      }
    }
    final byte[] keyBytes = KeyUtil.getKeyBytes(key);
    int bufSize = MIN_RECV_PACKET + keyBytes.length + val.length;

    //  # magic, opcode, keylen, extralen, datatype, [reserved],
    //    bodylen, opaque, cas
    //  REQ_PKT_FMT=">BBHBBxxIIQ"

    // set up the initial header stuff
    ByteBuffer bb = ByteBuffer.allocate(bufSize + extraLen);
    assert bb.order() == ByteOrder.BIG_ENDIAN;
    bb.put(REQ_MAGIC);
    bb.put((byte) cmd);
    bb.putShort((short) keyBytes.length);
    bb.put((byte) extraLen);
    bb.put((byte) 0); // data type
    bb.putShort((short) 0); // reserved
    bb.putInt(keyBytes.length + val.length + extraLen);
    bb.putInt(opaque);
    bb.putLong(cas);

    // Add the extra headers.
    for (Object o : extraHeaders) {
      if (o instanceof Integer) {
        bb.putInt((Integer) o);
      } else if (o instanceof byte[]) {
        bb.put((byte[]) o);
      } else if (o instanceof Long) {
        bb.putLong((Long) o);
      } else {
        assert false : "Unhandled extra header type:  " + o.getClass();
      }
    }

    // Add the normal stuff
    bb.put(keyBytes);
    bb.put(val);

    ((Buffer) bb).flip();
    setBuffer(bb);
  }

  /**
   * Generate an opaque ID.
   */
  static int generateOpaque() {
    int rv = seqNumber.incrementAndGet();
    while (rv < 0) {
      seqNumber.compareAndSet(rv, 0);
      rv = seqNumber.incrementAndGet();
    }
    return rv;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy