/**
   * @param keyBytes: keyName strings serialized as bytes eg. 'cluster.xml'
   * @return List of values (only 1 for Metadata) versioned byte[] eg. UTF bytes for cluster xml
   *     definitions
   * @throws VoldemortException
   */
  public List<Versioned<byte[]>> get(ByteArray keyBytes) throws VoldemortException {
    try {
      String key = ByteUtils.getString(keyBytes.get(), "UTF-8");

      if (METADATA_KEYS.contains(key)) {
        List<Versioned<byte[]>> values = Lists.newArrayList();

        // Get the cached value and convert to string
        Versioned<String> value = convertObjectToString(key, metadataCache.get(key));

        values.add(
            new Versioned<byte[]>(
                ByteUtils.getBytes(value.getValue(), "UTF-8"), value.getVersion()));

        return values;
      } else {
        throw new VoldemortException("Unhandled Key:" + key + " for MetadataStore get()");
      }
    } catch (Exception e) {
      throw new VoldemortException(
          "Failed to read metadata key:"
              + ByteUtils.getString(keyBytes.get(), "UTF-8")
              + " delete config/.temp config/.version directories and restart.",
          e);
    }
  }
  /**
   * A write through put to inner-store.
   *
   * @param keyBytes: keyName strings serialized as bytes eg. 'cluster.xml'
   * @param valueBytes: versioned byte[] eg. UTF bytes for cluster xml definitions
   * @throws VoldemortException
   */
  public synchronized void put(ByteArray keyBytes, Versioned<byte[]> valueBytes)
      throws VoldemortException {
    String key = ByteUtils.getString(keyBytes.get(), "UTF-8");
    Versioned<String> value =
        new Versioned<String>(
            ByteUtils.getString(valueBytes.getValue(), "UTF-8"), valueBytes.getVersion());

    Versioned<Object> valueObject = convertStringToObject(key, value);

    this.put(key, valueObject);
  }
  public VAdminProto.GetMetadataResponse handleGetMetadata(VAdminProto.GetMetadataRequest request) {
    VAdminProto.GetMetadataResponse.Builder response = VAdminProto.GetMetadataResponse.newBuilder();

    try {
      ByteArray key = ProtoUtils.decodeBytes(request.getKey());
      String keyString = ByteUtils.getString(key.get(), "UTF-8");
      if (MetadataStore.METADATA_KEYS.contains(keyString)) {
        List<Versioned<byte[]>> versionedList = metadataStore.get(key);
        int size = (versionedList.size() > 0) ? 1 : 0;

        if (size > 0) {
          Versioned<byte[]> versioned = versionedList.get(0);
          response.setVersion(ProtoUtils.encodeVersioned(versioned));
        }
      } else {
        throw new VoldemortException(
            "Metadata Key passed " + keyString + " is not handled yet ...");
      }
    } catch (VoldemortException e) {
      response.setError(ProtoUtils.encodeError(errorCodeMapper, e));
      logger.error("handleGetMetadata failed for request(" + request.toString() + ")", e);
    }

    return response.build();
  }
  public byte[] getValidValue(ByteArray key) {
    String keyString = ByteUtils.getString(key.get(), "UTF-8");
    if (MetadataStore.CLUSTER_KEY.equals(keyString)) {
      return ByteUtils.getBytes(
          new ClusterMapper().writeCluster(ServerTestUtils.getLocalCluster(1)), "UTF-8");
    } else if (MetadataStore.STORES_KEY.equals(keyString)) {
      return ByteUtils.getBytes(
          new StoreDefinitionsMapper().writeStoreList(ServerTestUtils.getStoreDefs(1)), "UTF-8");

    } else if (MetadataStore.SERVER_STATE_KEY.equals(keyString)) {
      int i = (int) (Math.random() * VoldemortState.values().length);
      return ByteUtils.getBytes(VoldemortState.values()[i].toString(), "UTF-8");
    } else if (MetadataStore.REBALANCING_STEAL_INFO.equals(keyString)) {
      int size = (int) (Math.random() * 10);
      List<Integer> partition = new ArrayList<Integer>();
      for (int i = 0; i < size; i++) {
        partition.add((int) Math.random() * 10);
      }

      return ByteUtils.getBytes(
          new RebalancerState(
                  Arrays.asList(
                      new RebalancePartitionsInfo(
                          0,
                          (int) Math.random() * 5,
                          partition,
                          new ArrayList<Integer>(0),
                          new ArrayList<Integer>(0),
                          Arrays.asList("testStoreName"),
                          new HashMap<String, String>(),
                          new HashMap<String, String>(),
                          (int) Math.random() * 3)))
              .toJsonString(),
          "UTF-8");
    } else if (MetadataStore.GRANDFATHERING_INFO.equals(keyString)) {
      int size = (int) (Math.random() * 10);
      List<Integer> partition = new ArrayList<Integer>();
      for (int i = 0; i < size; i++) {
        partition.add((int) Math.random() * 10);
      }
      return ByteUtils.getBytes(
          new GrandfatherState(
                  Arrays.asList(
                      new RebalancePartitionsInfo(
                          0,
                          (int) Math.random() * 5,
                          partition,
                          new ArrayList<Integer>(0),
                          new ArrayList<Integer>(0),
                          Arrays.asList("testStoreName"),
                          new HashMap<String, String>(),
                          new HashMap<String, String>(),
                          (int) Math.random() * 3)))
              .toJsonString(),
          "UTF-8");
    }

    throw new RuntimeException("Unhandled key:" + keyString + " passed");
  }
  private void checkValues(Versioned<byte[]> value, List<Versioned<byte[]>> list, ByteArray key) {
    assertEquals("should return exactly one value ", 1, list.size());

    assertEquals(
        "should return the last saved version", value.getVersion(), list.get(0).getVersion());
    assertEquals(
        "should return the last saved value (key:" + ByteUtils.getString(key.get(), "UTF-8") + ")",
        new String(value.getValue()),
        new String(list.get(0).getValue()));
  }
  public VAdminProto.UpdateMetadataResponse handleUpdateMetadata(
      VAdminProto.UpdateMetadataRequest request) {
    VAdminProto.UpdateMetadataResponse.Builder response =
        VAdminProto.UpdateMetadataResponse.newBuilder();

    try {
      ByteArray key = ProtoUtils.decodeBytes(request.getKey());
      String keyString = ByteUtils.getString(key.get(), "UTF-8");

      if (MetadataStore.METADATA_KEYS.contains(keyString)) {
        Versioned<byte[]> versionedValue = ProtoUtils.decodeVersioned(request.getVersioned());
        metadataStore.put(new ByteArray(ByteUtils.getBytes(keyString, "UTF-8")), versionedValue);
      }
    } catch (VoldemortException e) {
      response.setError(ProtoUtils.encodeError(errorCodeMapper, e));
      logger.error("handleUpdateMetadata failed for request(" + request.toString() + ")", e);
    }

    return response.build();
  }
  public byte[] getValidValue(ByteArray key) {
    String keyString = ByteUtils.getString(key.get(), "UTF-8");
    if (MetadataStore.CLUSTER_KEY.equals(keyString)
        || MetadataStore.REBALANCING_SOURCE_CLUSTER_XML.equals(keyString)) {
      return ByteUtils.getBytes(
          new ClusterMapper().writeCluster(ServerTestUtils.getLocalCluster(1)), "UTF-8");
    } else if (MetadataStore.STORES_KEY.equals(keyString)) {
      return ByteUtils.getBytes(
          new StoreDefinitionsMapper().writeStoreList(ServerTestUtils.getStoreDefs(1)), "UTF-8");

    } else if (MetadataStore.SERVER_STATE_KEY.equals(keyString)) {
      int i = (int) (Math.random() * VoldemortState.values().length);
      return ByteUtils.getBytes(VoldemortState.values()[i].toString(), "UTF-8");
    } else if (MetadataStore.REBALANCING_STEAL_INFO.equals(keyString)) {
      int size = (int) (Math.random() * 10) + 1;
      List<Integer> partition = new ArrayList<Integer>();
      for (int i = 0; i < size; i++) {
        partition.add((int) Math.random() * 10);
      }

      List<Integer> partitionIds = partition;

      HashMap<String, List<Integer>> storeToReplicaToPartitionList = Maps.newHashMap();
      storeToReplicaToPartitionList.put("test", partitionIds);

      return ByteUtils.getBytes(
          new RebalancerState(
                  Arrays.asList(
                      new RebalanceTaskInfo(
                          0,
                          (int) Math.random() * 5,
                          storeToReplicaToPartitionList,
                          ServerTestUtils.getLocalCluster(1))))
              .toJsonString(),
          "UTF-8");
    }

    throw new RuntimeException("Unhandled key:" + keyString + " passed");
  }
  public VAdminProto.AddStoreResponse handleAddStore(VAdminProto.AddStoreRequest request) {
    VAdminProto.AddStoreResponse.Builder response = VAdminProto.AddStoreResponse.newBuilder();

    // don't try to add a store in the middle of rebalancing
    if (metadataStore
            .getServerState()
            .equals(MetadataStore.VoldemortState.REBALANCING_MASTER_SERVER)
        || metadataStore
            .getServerState()
            .equals(MetadataStore.VoldemortState.REBALANCING_CLUSTER)) {
      response.setError(
          ProtoUtils.encodeError(
              errorCodeMapper, new VoldemortException("Rebalancing in progress")));
      return response.build();
    }

    try {
      // adding a store requires decoding the passed in store string
      StoreDefinitionsMapper mapper = new StoreDefinitionsMapper();
      StoreDefinition def = mapper.readStore(new StringReader(request.getStoreDefinition()));

      synchronized (lock) {
        // only allow a single store to be created at a time. We'll see concurrent errors when
        // writing the
        // stores.xml file out otherwise. (see ConfigurationStorageEngine.put for details)

        if (!storeRepository.hasLocalStore(def.getName())) {
          // open the store
          storageService.openStore(def);

          // update stores list in metadata store (this also has the
          // effect of updating the stores.xml file)
          List<StoreDefinition> currentStoreDefs;
          List<Versioned<byte[]>> v = metadataStore.get(MetadataStore.STORES_KEY);

          if (((v.size() > 0) ? 1 : 0) > 0) {
            Versioned<byte[]> currentValue = v.get(0);
            currentStoreDefs =
                mapper.readStoreList(
                    new StringReader(ByteUtils.getString(currentValue.getValue(), "UTF-8")));
          } else {
            currentStoreDefs = Lists.newArrayList();
          }
          currentStoreDefs.add(def);
          try {
            metadataStore.put(MetadataStore.STORES_KEY, currentStoreDefs);
          } catch (Exception e) {
            throw new VoldemortException(e);
          }
        } else {
          throw new StoreOperationFailureException(
              String.format("Store '%s' already exists on this server", def.getName()));
        }
      }
    } catch (VoldemortException e) {
      response.setError(ProtoUtils.encodeError(errorCodeMapper, e));
      logger.error("handleAddStore failed for request(" + request.toString() + ")", e);
    }

    return response.build();
  }