Package com.google.appengine.api.memcache

Source Code of com.google.appengine.api.memcache.AsyncMemcacheServiceImpl

// Copyright 2011 Google Inc.  All rights reserved

package com.google.appengine.api.memcache;

import static com.google.appengine.api.memcache.MemcacheServiceApiHelper.makeAsyncCall;

import com.google.appengine.api.NamespaceManager;
import com.google.appengine.api.memcache.MemcacheSerialization.ValueAndFlags;
import com.google.appengine.api.memcache.MemcacheService.CasValues;
import com.google.appengine.api.memcache.MemcacheService.IdentifiableValue;
import com.google.appengine.api.memcache.MemcacheService.SetPolicy;
import com.google.appengine.api.memcache.MemcacheServiceApiHelper.Provider;
import com.google.appengine.api.memcache.MemcacheServiceApiHelper.RpcResponseHandler;
import com.google.appengine.api.memcache.MemcacheServiceApiHelper.Transformer;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheBatchIncrementRequest;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheBatchIncrementResponse;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheDeleteRequest;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheDeleteResponse;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheDeleteResponse.DeleteStatusCode;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheFlushRequest;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheFlushResponse;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheGetRequest;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheGetResponse;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheIncrementRequest;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheIncrementRequest.Direction;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheIncrementResponse;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheIncrementResponse.IncrementStatusCode;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheServiceError;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheSetRequest;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheSetResponse;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheSetResponse.SetStatusCode;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheStatsRequest;
import com.google.appengine.api.memcache.MemcacheServicePb.MemcacheStatsResponse;
import com.google.appengine.api.memcache.MemcacheServicePb.MergedNamespaceStats;
import com.google.appengine.api.utils.FutureWrapper;
import com.google.apphosting.api.ApiProxy;
import com.google.common.base.StringUtil;
import com.google.common.primitives.Bytes;
import com.google.protobuf.ByteString;
import com.google.protobuf.Message;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
* Java bindings for the AsyncMemcache service.
*
*/
class AsyncMemcacheServiceImpl extends BaseMemcacheServiceImpl implements AsyncMemcacheService {

  static class StatsImpl implements Stats {
    private final long hits, misses, bytesFetched, items, bytesStored;
    private final int maxCachedTime;

    StatsImpl(MergedNamespaceStats stats) {
      if (stats != null) {
        hits = stats.getHits();
        misses = stats.getMisses();
        bytesFetched = stats.getByteHits();
        items = stats.getItems();
        bytesStored = stats.getBytes();
        maxCachedTime = stats.getOldestItemAge();
      } else {
        hits = misses = bytesFetched = items = bytesStored = 0;
        maxCachedTime = 0;
      }
    }

    @Override
    public long getHitCount() {
      return hits;
    }

    @Override
    public long getMissCount() {
      return misses;
    }

    @Override
    public long getBytesReturnedForHits() {
      return bytesFetched;
    }

    @Override
    public long getItemCount() {
      return items;
    }

    @Override
    public long getTotalItemBytes() {
      return bytesStored;
    }

    @Override
    public int getMaxTimeWithoutAccess() {
      return maxCachedTime;
    }

    @Override
    public String toString() {
      StringBuilder builder = new StringBuilder();
      builder.append("Hits: ").append(hits).append('\n');
      builder.append("Misses: ").append(misses).append('\n');
      builder.append("Bytes Fetched: ").append(bytesFetched).append('\n');
      builder.append("Bytes Stored: ").append(bytesStored).append('\n');
      builder.append("Items: ").append(items).append('\n');
      builder.append("Max Cached Time: ").append(maxCachedTime).append('\n');
      return builder.toString();
    }
  }

  static final class IdentifiableValueImpl implements IdentifiableValue {
    private final Object value;
    private final long casId;

    IdentifiableValueImpl(Object value, long casId) {
      this.value = value;
      this.casId = casId;
    }

    @Override
    public Object getValue() {
      return value;
    }

    long getCasId() {
      return casId;
    }

    @Override
    public boolean equals(Object otherObj) {
      if (this == otherObj) {
        return true;
      }
      if ((otherObj == null) || (getClass() != otherObj.getClass())) {
        return false;
      }
      IdentifiableValueImpl otherIdentifiableValue = (IdentifiableValueImpl) otherObj;
      return Objects.equals(value, otherIdentifiableValue.value) &&
          (casId == otherIdentifiableValue.casId);
    }

    @Override
    public int hashCode() {
      return Objects.hash(value, casId);
    }
  }

  private static class DefaultValueProviders {

    @SuppressWarnings("rawtypes")
    private static final Provider NULL_PROVIDER = new Provider() {
          @Override public Object get() {
            return null;
          }
        };

    private static final Provider<Boolean> FALSE_PROVIDER = new Provider<Boolean>() {
          @Override public Boolean get() {
            return Boolean.FALSE;
          }
        };

    @SuppressWarnings("rawtypes")
    private static final Provider SET_PROVIDER = new Provider<Set<?>>() {
          @Override public Set<?> get() {
            return new HashSet(0, 1);
          }
        };

    @SuppressWarnings("rawtypes")
    private static final Provider MAP_PROVIDER = new Provider<Map<?, ?>>() {
          @Override public Map<?, ?> get() {
            return new HashMap(0, 1);
          }
        };

    private static final Provider<Stats> STATS_PROVIDER = new Provider<Stats>() {
          final Stats emptyStats =  new AsyncMemcacheServiceImpl.StatsImpl(null);

          @Override public Stats get() {
            return emptyStats;
          }
        };

    static Provider<Boolean> falseValue() {
      return FALSE_PROVIDER;
    }

    @SuppressWarnings("unchecked")
    static <T> Provider<T> nullValue() {
      return NULL_PROVIDER;
    }

    @SuppressWarnings("unchecked")
    static <T> Provider<Set<T>> emptySet() {
      return SET_PROVIDER;
    }

    @SuppressWarnings("unchecked")
    static <K, V> Provider<Map<K, V>> emptyMap() {
      return MAP_PROVIDER;
    }

    static Provider<Stats> emptyStats() {
      return STATS_PROVIDER;
    }
  }

  private static class VoidFutureWrapper<K> extends FutureWrapper<K, Void> {

    private VoidFutureWrapper(Future<K> parent) {
      super(parent);
    }

    private static <K> Future<Void> wrap(Future<K> parent) {
      return new VoidFutureWrapper<K>(parent);
    }

    @Override
    protected Void wrap(K value) {
      return null;
    }

    @Override
    protected Throwable convertException(Throwable cause) {
      return cause;
    }
  }

  private static final class KeyValuePair<K, V> {
    private final K key;
    private final V value;

    private KeyValuePair(K key, V value) {
      this.key = key;
      this.value = value;
    }

    static <K, V> KeyValuePair<K, V> of(K key, V value) {
      return new KeyValuePair<K, V>(key, value);
    }
  }

  AsyncMemcacheServiceImpl(String namespace) {
    super(namespace);
  }

  static <T, V> Map<T, V> makeMap(Collection<T> keys, V value) {
    Map<T, V> map = new LinkedHashMap<T, V>(keys.size(), 1);
    for (T key : keys) {
      map.put(key, value);
    }
    return map;
  }

  private static ByteString makePbKey(Object key) {
    try {
      return ByteString.copyFrom(MemcacheSerialization.makePbKey(key));
    } catch (IOException ex) {
      throw new IllegalArgumentException("Cannot use as a key: '" + key + "'", ex);
    }
  }

  private static ValueAndFlags serializeValue(Object value) {
    try {
      return MemcacheSerialization.serialize(value);
    } catch (IOException ex) {
      throw new IllegalArgumentException("Cannot use as value: '" + value + "'", ex);
    }
  }

  private Object deserializeItem(Object key, MemcacheGetResponse.Item item) {
    try {
      return MemcacheSerialization.deserialize(item.getValue().toByteArray(), item.getFlags());
    } catch (ClassNotFoundException ex) {
      getErrorHandler().handleDeserializationError(
          new InvalidValueException("Can't find class for value of key '" + key + "'", ex));
    } catch (IOException ex) {
      getErrorHandler().handleDeserializationError(
          new InvalidValueException("Failed to parse the value for '" + key + "'", ex));
    }
    return null;
  }

  private <M extends Message, T> RpcResponseHandler<M, T> createRpcResponseHandler(
      M response, String errorText, Transformer<M, T> responseTransformer) {
    return new RpcResponseHandler<M, T>(
        response, errorText, responseTransformer, getErrorHandler());
  }

  private <T> RpcResponseHandlerForPut<T> createRpcResponseHandlerForPut(
      Iterable<MemcacheSetRequest.Item.Builder> itemBuilders, String namespace,
      MemcacheSetResponse response, String errorText,
      Transformer<MemcacheSetResponse, T> responseTransformer) {
    return new RpcResponseHandlerForPut<T>(
        itemBuilders, namespace, response, errorText, responseTransformer);
  }

  private class RpcResponseHandlerForPut<T> extends RpcResponseHandler<MemcacheSetResponse, T> {

    /**
     * We remember what was sent to the backend, so that if we get an error response we can test to
     * see if the error was caused by the API being given invalid values.
     */
    private final Iterable<MemcacheSetRequest.Item.Builder> itemsSentToBackend;
    private final String namespace;

    RpcResponseHandlerForPut(Iterable<MemcacheSetRequest.Item.Builder> itemsSentToBackend,
        String namespace,
        MemcacheSetResponse response, String errorText,
        Transformer<MemcacheSetResponse, T> responseTransfomer) {
      super(response, errorText, responseTransfomer, getErrorHandler());
      this.itemsSentToBackend = itemsSentToBackend;
      this.namespace = namespace;
    }

    /**
     * When we get an error from the backend we check to see if it could possibly have been caused
     * by an invalid key or value being passed into the API from the app. If so we test the original
     * key and value passed in and if we find them to be invalid we throw an exception that is more
     * informative to the app writer than the default exception.
     */
    @Override
    void handleApiProxyException(Throwable cause) throws Exception {
      if (cause instanceof ApiProxy.ApplicationException) {
        ApiProxy.ApplicationException applicationException = (ApiProxy.ApplicationException) cause;
        if (applicationException.getApplicationError()
            == MemcacheServiceError.ErrorCode.UNSPECIFIED_ERROR_VALUE) {
          handleApiProxyException(applicationException.getErrorDetail());
        }
      } else if (cause instanceof MemcacheServiceException){
        handleApiProxyException(cause.getMessage());
      }
      super.handleApiProxyException(cause);
    }

    private void handleApiProxyException(String msg) throws MemcacheServiceException {
      for (MemcacheSetRequest.Item.Builder itemBuilder : itemsSentToBackend) {
        ByteString pbKey = itemBuilder.getKey();
        ByteString value = itemBuilder.getValue();
        if (value.size() + pbKey.size() > ITEM_SIZE_LIMIT) {
          maybeThrow("Key+value is bigger than maximum allowed. " + msg);
        }
        if (Bytes.contains(pbKey.toByteArray(), (byte) 0)) {
          maybeThrow("Key contains embedded null byte. " + msg);
        }
        if (namespace != null) {
          try {
            NamespaceManager.validateNamespace(namespace);
          } catch (IllegalArgumentException ex) {
            maybeThrow(ex.toString());
          }
        }
      }
    }

    /** Will throw exception or just log it, depending on what error handler is set.*/
    private void maybeThrow(String msg) {
      getErrorHandler().handleServiceError(new MemcacheServiceException(msg));
    }
  }

  /**
   * Matches limit documented in
   * https://developers.google.com/appengine/docs/python/memcache/#Python_Limits
   *
   * TODO(user) This is too conservative; need to revisit. The docs are conflating item size
   * limit with total cache size accounting.
   */
  static final int ITEM_SIZE_LIMIT = 1024 * 1024 - 96;

  @Override
  public Future<Boolean> contains(Object key) {
    return doGet(key, false, "Memcache contains: exception testing contains (" + key + ")",
        new Transformer<MemcacheGetResponse, Boolean>() {
          @Override public Boolean transform(MemcacheGetResponse response) {
            return response.getItemCount() > 0;
          }
        }, DefaultValueProviders.falseValue());
  }

  private <T> Future<T> doGet(Object key, boolean forCas, String errorText,
      final Transformer<MemcacheGetResponse, T> responseTransfomer, Provider<T> defaultValue) {
    MemcacheGetRequest.Builder requestBuilder = MemcacheGetRequest.newBuilder();
    requestBuilder.addKey(makePbKey(key));
    requestBuilder.setNameSpace(getEffectiveNamespace());
    if (forCas) {
      requestBuilder.setForCas(true);
    }
    return makeAsyncCall("Get", requestBuilder.build(),
        createRpcResponseHandler(MemcacheGetResponse.getDefaultInstance(), errorText,
            responseTransfomer), defaultValue);
  }

  @Override
  public Future<Object> get(final Object key) {
    return doGet(key, false, "Memcache get: exception getting 1 key (" + key + ")",
        new Transformer<MemcacheGetResponse, Object>() {
          @Override public Object transform(MemcacheGetResponse response) {
            return response.getItemCount() == 0 ? null : deserializeItem(key, response.getItem(0));
          }
        }, DefaultValueProviders.nullValue());
  }

  @Override
  public Future<IdentifiableValue> getIdentifiable(final Object key) {
    return doGet(key, true, "Memcache getIdentifiable: exception getting 1 key (" + key + ")",
        new Transformer<MemcacheGetResponse, IdentifiableValue>() {
          @Override public IdentifiableValue transform(MemcacheGetResponse response) {
            if (response.getItemCount() == 0) {
              return null;
            }
            MemcacheGetResponse.Item item = response.getItem(0);
            return new IdentifiableValueImpl(deserializeItem(key, item), item.getCasId());
          }
        }, DefaultValueProviders.<IdentifiableValue>nullValue());
  }

  @Override
  public <K> Future<Map<K, IdentifiableValue>> getIdentifiables(Collection<K> keys) {
    return doGetAll(keys, true,
        "Memcache getIdentifiables: exception getting multiple keys",
        new Transformer<KeyValuePair<K, MemcacheGetResponse.Item>, IdentifiableValue>() {
          @Override
          public IdentifiableValue transform(KeyValuePair<K, MemcacheGetResponse.Item> pair) {
            MemcacheGetResponse.Item item = pair.value;
            Object value = deserializeItem(pair.key, item);
            return new IdentifiableValueImpl(value, item.getCasId());
          }
        }, DefaultValueProviders.<K, IdentifiableValue>emptyMap());
  }

  @Override
  public <K> Future<Map<K, Object>> getAll(Collection<K> keys) {
    return doGetAll(keys, false,
        "Memcache getAll: exception getting multiple keys",
        new Transformer<KeyValuePair<K, MemcacheGetResponse.Item>, Object>() {
          @Override
          public Object transform(KeyValuePair<K, MemcacheGetResponse.Item> pair) {
            return deserializeItem(pair.key, pair.value);
          }
        }, DefaultValueProviders.<K, Object>emptyMap());
  }

  private <K, V> Future<Map<K, V>> doGetAll(Collection<K> keys, boolean forCas,
      String errorText,
      final Transformer<KeyValuePair<K, MemcacheGetResponse.Item>, V> responseTransfomer,
      Provider<Map<K, V>> defaultValue) {
    MemcacheGetRequest.Builder requestBuilder = MemcacheGetRequest.newBuilder();
    requestBuilder.setNameSpace(getEffectiveNamespace());
    final Map<ByteString, K> byteStringToKey = new HashMap<ByteString, K>(keys.size(), 1);
    for (K key : keys) {
      ByteString pbKey = makePbKey(key);
      byteStringToKey.put(pbKey, key);
      requestBuilder.addKey(pbKey);
    }
    if (forCas) {
      requestBuilder.setForCas(forCas);
    }
    return makeAsyncCall("Get", requestBuilder.build(), createRpcResponseHandler(
        MemcacheGetResponse.getDefaultInstance(), errorText,
        new Transformer<MemcacheGetResponse, Map<K, V>>() {
          @Override
          public Map<K, V> transform(MemcacheGetResponse response) {
            Map<K, V> result = new HashMap<K, V>();
            for (MemcacheGetResponse.Item item : response.getItemList()) {
              K key = byteStringToKey.get(item.getKey());
              V obj = responseTransfomer.transform(KeyValuePair.of(key, item));
              result.put(key, obj);
            }
            return result;
          }
        }), defaultValue);
  }

  /** Use this to make sure we don't write arbitrarily big log messages. */
  private static final int MAX_LOGGED_VALUE_SIZE = 100;

  /**
   * Note: non-null oldValue implies Compare-and-Swap operation.
   */
  private Future<Boolean> doPut(final Object key, IdentifiableValue oldValue, Object value,
      Expiration expires, MemcacheSetRequest.SetPolicy policy) {
    MemcacheSetRequest.Builder requestBuilder = MemcacheSetRequest.newBuilder();
    requestBuilder.setNameSpace(getEffectiveNamespace());
    MemcacheSetRequest.Item.Builder itemBuilder = MemcacheSetRequest.Item.newBuilder();
    ValueAndFlags vaf = serializeValue(value);
    itemBuilder.setValue(ByteString.copyFrom(vaf.value));
    itemBuilder.setFlags(vaf.flags.ordinal());
    itemBuilder.setKey(makePbKey(key));
    itemBuilder.setExpirationTime(expires == null ? 0 : expires.getSecondsValue());
    itemBuilder.setSetPolicy(policy);
    if (policy == MemcacheSetRequest.SetPolicy.CAS) {
      if (oldValue == null) {
        throw new IllegalArgumentException("oldValue must not be null.");
      }
      if (!(oldValue instanceof IdentifiableValueImpl)) {
        throw new IllegalArgumentException(
            "oldValue is an instance of an unapproved IdentifiableValue implementation.  " +
            "Perhaps you implemented your own version of IdentifiableValue?  " +
            "If so, don't do this.");
      }
      itemBuilder.setCasId(((IdentifiableValueImpl) oldValue).getCasId());
    }
    requestBuilder.addItem(itemBuilder);

    String valueAsString =
        StringUtil.truncateAtMaxLength(String.valueOf(value), MAX_LOGGED_VALUE_SIZE, true);
    return makeAsyncCall("Set", requestBuilder.build(), createRpcResponseHandlerForPut(
        Arrays.asList(itemBuilder),
        requestBuilder.getNameSpace(),
        MemcacheSetResponse.getDefaultInstance(),
        String.format("Memcache put: exception setting 1 key (%s) to '%s'", key, valueAsString),
        new Transformer<MemcacheSetResponse, Boolean>() {
          @Override public Boolean transform(MemcacheSetResponse response) {
            if (response.getSetStatusCount() != 1) {
              throw new MemcacheServiceException("Memcache put: Set one item, got "
                  + response.getSetStatusCount() + " response statuses");
            }
            SetStatusCode status = response.getSetStatus(0);
            if (status == SetStatusCode.ERROR) {
              throw new MemcacheServiceException(
                  "Memcache put: Error setting single item (" + key + ")");
            }
            return status == SetStatusCode.STORED;
          }
        }), DefaultValueProviders.falseValue());
  }

  private static MemcacheSetRequest.SetPolicy convertSetPolicyToPb(SetPolicy policy) {
    switch (policy) {
      case SET_ALWAYS:
        return MemcacheSetRequest.SetPolicy.SET;
      case ADD_ONLY_IF_NOT_PRESENT:
        return MemcacheSetRequest.SetPolicy.ADD;
      case REPLACE_ONLY_IF_PRESENT:
        return MemcacheSetRequest.SetPolicy.REPLACE;
    }
    throw new IllegalArgumentException("Unknown policy: " + policy);
  }

  @Override
  public Future<Boolean> put(Object key, Object value, Expiration expires, SetPolicy policy) {
    return doPut(key, null, value, expires, convertSetPolicyToPb(policy));
  }

  @Override
  public Future<Void> put(Object key, Object value, Expiration expires) {
    return VoidFutureWrapper.wrap(
        doPut(key, null, value, expires, MemcacheSetRequest.SetPolicy.SET));
  }

  @Override
  public Future<Void> put(Object key, Object value) {
    return VoidFutureWrapper.wrap(
        doPut(key, null, value, null, MemcacheSetRequest.SetPolicy.SET));
  }

  @Override
  public Future<Boolean> putIfUntouched(Object key, IdentifiableValue oldValue,
      Object newValue, Expiration expires) {
    return doPut(key, oldValue, newValue, expires, MemcacheSetRequest.SetPolicy.CAS);
  }

  @Override
  public Future<Boolean> putIfUntouched(Object key, IdentifiableValue oldValue, Object newValue) {
    return doPut(key, oldValue, newValue, null, MemcacheSetRequest.SetPolicy.CAS);
  }

  @Override
  public <T> Future<Set<T>> putIfUntouched(Map<T, CasValues> values) {
    return doPutAll(values, null, MemcacheSetRequest.SetPolicy.CAS, "putIfUntouched");
  }

  @Override
  public <T> Future<Set<T>> putIfUntouched(Map<T, CasValues> values, Expiration expiration) {
    return doPutAll(values, expiration, MemcacheSetRequest.SetPolicy.CAS, "putIfUntouched");
  }

  private <T> Future<Set<T>> doPutAll(Map<T, ?> values, Expiration expires,
      MemcacheSetRequest.SetPolicy policy, String operation) {
    MemcacheSetRequest.Builder requestBuilder = MemcacheSetRequest.newBuilder();
    requestBuilder.setNameSpace(getEffectiveNamespace());
    final List<T> requestedKeys = new ArrayList<T>(values.size());
    for (Map.Entry<T, ?> entry : values.entrySet()) {
      MemcacheSetRequest.Item.Builder itemBuilder = MemcacheSetRequest.Item.newBuilder();
      requestedKeys.add(entry.getKey());
      itemBuilder.setKey(makePbKey(entry.getKey()));
      ValueAndFlags vaf;
      if (policy == MemcacheSetRequest.SetPolicy.CAS) {
        CasValues value = (CasValues) entry.getValue();
        if (value == null) {
          throw new IllegalArgumentException(entry.getKey() + " has a null for CasValues");
        }
        vaf = serializeValue(value.getNewValue());
        if (!(value.getOldValue() instanceof IdentifiableValueImpl)) {
          throw new IllegalArgumentException(
            entry.getKey() + " CasValues has an oldValue instance of an unapproved " +
            "IdentifiableValue implementation.  Perhaps you implemented your own " +
            "version of IdentifiableValue?  If so, don't do this.");
        }
        itemBuilder.setCasId(((IdentifiableValueImpl) value.getOldValue()).getCasId());
        if (value.getExipration() != null) {
          itemBuilder.setExpirationTime(value.getExipration().getSecondsValue());
        } else {
          itemBuilder.setExpirationTime(expires == null ? 0 : expires.getSecondsValue());
        }
      } else {
        vaf = serializeValue(entry.getValue());
        itemBuilder.setExpirationTime(expires == null ? 0 : expires.getSecondsValue());
      }
      itemBuilder.setValue(ByteString.copyFrom(vaf.value));
      itemBuilder.setFlags(vaf.flags.ordinal());
      itemBuilder.setSetPolicy(policy);
      requestBuilder.addItem(itemBuilder);
    }
    return makeAsyncCall("Set", requestBuilder.build(), createRpcResponseHandlerForPut(
        requestBuilder.getItemBuilderList(),
        requestBuilder.getNameSpace(),
        MemcacheSetResponse.getDefaultInstance(),
        "Memcache " + operation + ": Unknown exception setting " + values.size() + " keys",
        new Transformer<MemcacheSetResponse, Set<T>>() {
          @Override public Set<T> transform(MemcacheSetResponse response) {
            if (response.getSetStatusCount() != requestedKeys.size()) {
              throw new MemcacheServiceException(String.format(
                  "Memcache put: Set %d items, got %d response statuses",
                  requestedKeys.size(),
                  response.getSetStatusCount()));
            }
            HashSet<T> result = new HashSet<T>();
            HashSet<T> errors = new HashSet<T>();
            Iterator<SetStatusCode> statusIter = response.getSetStatusList().iterator();
            for (T requestedKey : requestedKeys) {
              SetStatusCode status = statusIter.next();
              if (status == MemcacheSetResponse.SetStatusCode.ERROR) {
                errors.add(requestedKey);
              } else if (status == MemcacheSetResponse.SetStatusCode.STORED) {
                result.add(requestedKey);
              }
            }
            if (!errors.isEmpty()) {
              StringBuilder builder = new StringBuilder("Memcache put: Set failed to set ");
              builder.append(errors.size()).append(" keys: ");
              for (Object err : errors) {
                builder.append(err).append(", ");
              }
              builder.setLength(builder.length() - 2);
              throw new MemcacheServiceException(builder.toString());
            }
            return result;
          }
        }), DefaultValueProviders.<T>emptySet());
  }

  @Override
  public <T> Future<Set<T>> putAll(Map<T, ?> values, Expiration expires, SetPolicy policy) {
    return doPutAll(values, expires, convertSetPolicyToPb(policy), "putAll");
  }

  @Override
  public Future<Void> putAll(Map<?, ?> values, Expiration expires) {
    return VoidFutureWrapper.wrap(
        doPutAll(values, expires, MemcacheSetRequest.SetPolicy.SET, "putAll"));
  }

  @Override
  public Future<Void> putAll(Map<?, ?> values) {
    return VoidFutureWrapper.wrap(
        doPutAll(values, null, MemcacheSetRequest.SetPolicy.SET, "putAll"));
  }

  @Override
  public Future<Boolean> delete(Object key) {
    return delete(key, 0);
  }

  @Override
  public Future<Boolean> delete(Object key, long millisNoReAdd) {
    MemcacheDeleteRequest request = MemcacheDeleteRequest.newBuilder()
        .setNameSpace(getEffectiveNamespace())
        .addItem(MemcacheDeleteRequest.Item.newBuilder()
            .setKey(makePbKey(key))
            .setDeleteTime((int) TimeUnit.SECONDS.convert(millisNoReAdd, TimeUnit.MILLISECONDS)))
        .build();
    return makeAsyncCall("Delete", request, createRpcResponseHandler(
        MemcacheDeleteResponse.getDefaultInstance(),
        "Memcache delete: Unknown exception deleting key: " + key,
        new Transformer<MemcacheDeleteResponse, Boolean>() {
          @Override public Boolean transform(MemcacheDeleteResponse response) {
            return response.getDeleteStatus(0) == DeleteStatusCode.DELETED;
          }
        }), DefaultValueProviders.falseValue());
  }

  @Override
  public <T> Future<Set<T>> deleteAll(Collection<T> keys) {
    return deleteAll(keys, 0);
  }

  @Override
  public <T> Future<Set<T>> deleteAll(Collection<T> keys, long millisNoReAdd) {
    MemcacheDeleteRequest.Builder requestBuilder =
        MemcacheDeleteRequest.newBuilder().setNameSpace(getEffectiveNamespace());
    final List<T> requestedKeys = new ArrayList<T>(keys.size());
    for (T key : keys) {
      requestedKeys.add(key);
      requestBuilder.addItem(MemcacheDeleteRequest.Item.newBuilder()
                                 .setDeleteTime((int) (millisNoReAdd / 1000))
                                 .setKey(makePbKey(key)));
    }
    return makeAsyncCall("Delete", requestBuilder.build(), createRpcResponseHandler(
        MemcacheDeleteResponse.getDefaultInstance(),
        "Memcache deleteAll: Unknown exception deleting multiple keys",
        new Transformer<MemcacheDeleteResponse, Set<T>>() {
          @Override public Set<T> transform(MemcacheDeleteResponse response) {
            Set<T> retval = new LinkedHashSet<T>();
            Iterator<T> requestedKeysIter = requestedKeys.iterator();
            for (DeleteStatusCode deleteStatus : response.getDeleteStatusList()) {
              T requestedKey = requestedKeysIter.next();
              if (deleteStatus == DeleteStatusCode.DELETED) {
                retval.add(requestedKey);
              }
            }
            return retval;
          }
        }), DefaultValueProviders.<T>emptySet());
  }

  private static MemcacheIncrementRequest.Builder newIncrementRequestBuilder(
      Object key, long delta, Long initialValue) {
    MemcacheIncrementRequest.Builder requestBuilder = MemcacheIncrementRequest.newBuilder();
    requestBuilder.setKey(makePbKey(key));
    if (delta > 0) {
      requestBuilder.setDirection(Direction.INCREMENT);
      requestBuilder.setDelta(delta);
    } else {
      requestBuilder.setDirection(Direction.DECREMENT);
      requestBuilder.setDelta(-delta);
    }
    if (initialValue != null) {
      requestBuilder.setInitialValue(initialValue);
      requestBuilder.setInitialFlags(MemcacheSerialization.Flag.LONG.ordinal());
    }
    return requestBuilder;
  }

  @Override
  public Future<Long> increment(Object key, long delta) {
    return increment(key, delta, null);
  }

  @Override
  public Future<Long> increment(final Object key, long delta, Long initialValue) {
    MemcacheIncrementRequest request = newIncrementRequestBuilder(key, delta, initialValue)
        .setNameSpace(getEffectiveNamespace())
        .build();
    return makeAsyncCall("Increment", request,
        new RpcResponseHandler<MemcacheIncrementResponse, Long>(
            MemcacheIncrementResponse.getDefaultInstance(),
            "Memcache increment: exception when incrementing key '" + key + "'",
            new Transformer<MemcacheIncrementResponse, Long>() {
              @Override public Long transform(MemcacheIncrementResponse response) {
                return response.hasNewValue() ? response.getNewValue() : null;
              }
            }, getErrorHandler()) {
          @Override void handleApiProxyException(Throwable cause) throws Exception {
            if (cause instanceof ApiProxy.ApplicationException) {
              ApiProxy.ApplicationException applicationException =
                  (ApiProxy.ApplicationException) cause;
              getLogger().info(applicationException.getErrorDetail());
              if (applicationException.getApplicationError() ==
                  MemcacheServiceError.ErrorCode.INVALID_VALUE_VALUE) {
                throw new InvalidValueException("Non-incrementable value for key '" + key + "'");
              }
            }
            super.handleApiProxyException(cause);
          }
        }, DefaultValueProviders.<Long>nullValue());
  }

  @Override
  public <T> Future<Map<T, Long>> incrementAll(Collection<T> keys, long delta) {
    return incrementAll(keys, delta, null);
  }

  @Override
  public <T> Future<Map<T, Long>> incrementAll(Collection<T> keys, long delta, Long initialValue) {
    return incrementAll(makeMap(keys, delta), initialValue);
  }

  @Override
  public <T> Future<Map<T, Long>> incrementAll(Map<T, Long> offsets) {
    return incrementAll(offsets, null);
  }

  @Override
  public <T> Future<Map<T, Long>> incrementAll(Map<T, Long> offsets, Long initialValue) {
    MemcacheBatchIncrementRequest.Builder requestBuilder =
        MemcacheBatchIncrementRequest.newBuilder().setNameSpace(getEffectiveNamespace());
    final List<T> requestedKeys = new ArrayList<T>(offsets.size());
    for (Map.Entry<T, Long> entry : offsets.entrySet()) {
      requestedKeys.add(entry.getKey());
      requestBuilder.addItem(
          newIncrementRequestBuilder(entry.getKey(), entry.getValue(), initialValue));
    }
    return makeAsyncCall("BatchIncrement", requestBuilder.build(), createRpcResponseHandler(
        MemcacheBatchIncrementResponse.getDefaultInstance(),
        "Memcache incrmentAll: exception incrementing multiple keys",
        new Transformer<MemcacheBatchIncrementResponse, Map<T, Long>>() {
          @Override public Map<T, Long> transform(MemcacheBatchIncrementResponse response) {
            Map<T, Long> result = new LinkedHashMap<T, Long>(requestedKeys.size(), 1);
            Iterator<MemcacheIncrementResponse> items = response.getItemList().iterator();
            for (T requestedKey : requestedKeys) {
              MemcacheIncrementResponse item = items.next();
              if (item.getIncrementStatus().equals(IncrementStatusCode.OK) && item.hasNewValue()) {
                result.put(requestedKey, item.getNewValue());
              } else {
                result.put(requestedKey, null);
              }
            }
            return result;
          }
        }),
        new Provider<Map<T, Long>>() {
            @Override public Map<T, Long> get() {
              return makeMap(requestedKeys, null);
            }
          });
  }

  @Override
  public Future<Void> clearAll() {
    return makeAsyncCall("FlushAll", MemcacheFlushRequest.getDefaultInstance(),
        createRpcResponseHandler(MemcacheFlushResponse.getDefaultInstance(),
            "Memcache clearAll: exception",
            new Transformer<MemcacheFlushResponse, Void>() {
              @Override public Void transform(MemcacheFlushResponse response) {
                return null;
              }
            }), DefaultValueProviders.<Void>nullValue());
  }

  @Override
  public Future<Stats> getStatistics() {
    return makeAsyncCall("Stats",  MemcacheStatsRequest.getDefaultInstance(),
        createRpcResponseHandler(MemcacheStatsResponse.getDefaultInstance(),
            "Memcache getStatistics: exception",
            new Transformer<MemcacheStatsResponse, Stats>() {
              @Override public Stats transform(MemcacheStatsResponse response) {
                return new StatsImpl(response.getStats());
              }
            }), DefaultValueProviders.emptyStats());
  }
}
TOP

Related Classes of com.google.appengine.api.memcache.AsyncMemcacheServiceImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.