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

net.spy.memcached.protocol.ascii.BaseGetConfigOperationImpl Maven / Gradle / Ivy

/**
 * Copyright (C) 2012-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 
 *
 * SPDX-License-Identifier: Apache-2.0
 */
package net.spy.memcached.protocol.ascii;

import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Collections;

import net.spy.memcached.ConfigurationTypeUtil;
import net.spy.memcached.KeyUtil;
import net.spy.memcached.ops.ConfigurationType;
import net.spy.memcached.ops.GetAndTouchOperation;
import net.spy.memcached.ops.GetConfigOperation;
import net.spy.memcached.ops.GetOperation;
import net.spy.memcached.ops.GetlOperation;
import net.spy.memcached.ops.GetsOperation;
import net.spy.memcached.ops.OperationCallback;
import net.spy.memcached.ops.OperationState;
import net.spy.memcached.ops.OperationStatus;
import net.spy.memcached.util.StringUtils;

/**
 * Base class for getConfig handlers.
 */
abstract class BaseGetConfigOperationImpl extends OperationImpl {

  private static final OperationStatus END = new OperationStatus(true, "END");
  private static final OperationStatus NOT_FOUND = new OperationStatus(false,
      "NOT_FOUND");
  private static final OperationStatus LOCK_ERROR = new OperationStatus(false,
      "LOCK_ERROR");
  private static final byte[] RN_BYTES = "\r\n".getBytes();
  private final String cmd;
  protected final Collection types;
  private String currentType = null;
  private final int exp;
  private final boolean hasExp;
  private int currentFlags = 0;
  private byte[] data = null;
  private int readOffset = 0;
  private boolean hasValue;

  /*
   * Enum to track the state transition while reading off the buffers. Tracking each state
   *  is critical as the entire response might span multiple buffer reads. 
   */
  private enum BufferState{ 
    READ_CONFIG('*'),
    RESPONSE_END_START('\r'),
    RESPONSE_END_FINISH('\n'),
    READ_COMPLETED('\0');
    
    //The value mapping to the enum is used sometimes to help in state transition.
    private byte value;
    
    BufferState(char value){
      this.value = (byte)value;
    }
    
    public byte getValue(){
      return value;
    }
    
  }
  private BufferState lookingFor = BufferState.READ_CONFIG;
  
  public BaseGetConfigOperationImpl(String cmd, OperationCallback cb, Collection types) {
    super(cb);
    this.cmd = cmd;
    this.types = types;
    this.exp = 0;
    this.hasExp = false;
    this.hasValue = false;
  }

  public BaseGetConfigOperationImpl(String cmd, int e, OperationCallback cb, ConfigurationType type) {
    super(cb);
    this.cmd = cmd;
    this.types = Collections.singleton(type);
    this.exp = e;
    this.hasExp = true;
    this.hasValue = false;
  }

  /**
   * Get the types that this GetConfigOperation is looking for.
   */
  public final Collection getTypes() {
    return Collections.unmodifiableCollection(types);
  }

  @Override
  public final void handleLine(String line) {
    if (line.equals("END")) {
      getLogger().debug("Get complete!");
      if (this.hasValue) {
        getCallback().receivedStatus(END);
      } else {
        getCallback().receivedStatus(NOT_FOUND);
      }
      transitionState(OperationState.COMPLETE);
      data = null;
    } else if (line.startsWith("CONFIG ")) {
      getLogger().debug("Got line %s", line);
      String[] configPreamble = line.split(" ");
      assert configPreamble[0].equals("CONFIG");
      this.currentType = configPreamble[1];
      this.currentFlags = Integer.parseInt(configPreamble[2]);
      this.data = new byte[Integer.parseInt(configPreamble[3])];
      this.readOffset = 0;
      this.hasValue = true;
      getLogger().debug("Set read type to data");
      setReadType(OperationReadType.DATA);
    } else {
      assert false : "Unknown line type: " + line;
    }
  }

  @Override
  public final void handleRead(ByteBuffer readBuffer) {
    assert this.currentType != null;
    assert this.data != null;
    // This will be the case, because we'll clear them when it's not.
    assert this.readOffset <= this.data.length : "readOffset is " + this.readOffset
        + " data.length is " + this.data.length;

    getLogger().debug("readOffset: %d, length: %d", this.readOffset, this.data.length);
    // If we're not looking for termination, we're still looking for data
    if (this.lookingFor == BufferState.READ_CONFIG) {
      transferFromBuffer(readBuffer);
    }
    
    // Transition us into a ``looking for \r\n'' kind of state if we've
    // read enough and are still in a data state.
    if (this.readOffset == this.data.length && this.lookingFor == BufferState.READ_CONFIG) {
      updateCallBackWithResponse();
      this.lookingFor = BufferState.RESPONSE_END_START;
    }
    
    // If we're looking for an ending byte, let's go find it.
    if (this.lookingFor != BufferState.READ_CONFIG && readBuffer.hasRemaining()) {
      readResponseEnding(readBuffer);
      // Completed the read, reset stuff.
      if (this.lookingFor == BufferState.READ_COMPLETED) {
        resetAndChangeReadTypeToLine();
      }
    }
  }

  void transferFromBuffer(ByteBuffer readBuffer){
    int toRead = this.data.length - this.readOffset;
    int available = readBuffer.remaining();
    toRead = Math.min(toRead, available);
    getLogger().debug("Reading %d bytes", toRead);
    readBuffer.get(this.data, this.readOffset, toRead);
    this.readOffset += toRead;    
  }
  
  void updateCallBackWithResponse(){
    OperationCallback cb = getCallback();
    if (cb instanceof GetConfigOperation.Callback) {
      GetConfigOperation.Callback gcb = (GetConfigOperation.Callback) cb;
      gcb.gotData(ConfigurationType.valueOf(this.currentType.toUpperCase()), this.currentFlags, this.data);
    } else {
      throw new ClassCastException("Couldn't convert " + cb
          + "to a relevent op");
    }
  }
  
  void readResponseEnding(ByteBuffer readBuffer){
    do {
      byte tmp = readBuffer.get();
      assert tmp == this.lookingFor.getValue() : "Expecting " + this.lookingFor.getValue() + ", got "
          + (char) tmp;
      switch (this.lookingFor) {
      case RESPONSE_END_START:
        this.lookingFor = BufferState.RESPONSE_END_FINISH;
        break;
      case RESPONSE_END_FINISH:
        this.lookingFor = BufferState.READ_COMPLETED;
        break;
      default:
        assert false : "Looking for unexpected char: " + (char) this.lookingFor.getValue();
      }
    } while (this.lookingFor != BufferState.READ_COMPLETED && readBuffer.hasRemaining());    
  }
  
  void resetAndChangeReadTypeToLine(){
    this.currentType = null;
    this.data = null;
    this.lookingFor = BufferState.READ_CONFIG;
    this.readOffset = 0;
    this.currentFlags = 0;
    getLogger().debug("Setting read type back to line.");
    setReadType(OperationReadType.LINE);
  }

  @Override
  public final void initialize() {
    // Figure out the length of the request
    int size = 12; // Enough for config get\r\n
    Collection typeBytes = ConfigurationTypeUtil.getTypeBytes(this.types);
    for (byte[] t : typeBytes) {
      size += t.length;
      //Increment one more for the space after type.
      size++;
    }
    byte[] e = String.valueOf(this.exp).getBytes();
    if (this.hasExp) {
      size += e.length + 1;
    }
    ByteBuffer b = ByteBuffer.allocate(size);
    b.put(this.cmd.getBytes());
    for (byte[] t : typeBytes) {
      b.put((byte) ' ');
      b.put(t);
    }
    if (this.hasExp) {
      b.put((byte) ' ');
      b.put(e);
    }
    b.put(RN_BYTES);
    b.flip();
    setBuffer(b);
  }

  @Override
  protected final void wasCancelled() {
    getCallback().receivedStatus(CANCELLED);
  }

  @Override
  public String toString() {
    StringBuffer typeValues = new StringBuffer();
    for(ConfigurationType type : types){
      typeValues.append(" " + type.getValue());
    }
    
    return "Cmd: " + cmd + " Types: " + typeValues.toString() + "Exp: "
      + exp;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy