Package com.google.appengine.tools.remoteapi

Source Code of com.google.appengine.tools.remoteapi.TransactionBuilder

// Copyright 2010 Google Inc. All Rights Reserved.

package com.google.appengine.tools.remoteapi;

import com.google.apphosting.datastore.DatastoreV3Pb;
import com.google.apphosting.utils.remoteapi.RemoteApiPb;
import com.google.protobuf.ByteString;
import com.google.storage.onestore.v3.OnestoreEntity;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Nullable;

/**
* An in-progress transaction that will be sent via the remote API on commit.
*/
class TransactionBuilder {

  /**
   * A map containing a copy of each entity that we retrieved from the
   * datastore during this transaction. On commit, we will assert
   * that these entities haven't changed. If the value is null, the
   * datastore didn't return any entity for the given key, and we will
   * assert that the entity doesn't exist at commit time.
   */
  private final Map<ByteString, OnestoreEntity.EntityProto> getCache =
      new HashMap<ByteString, OnestoreEntity.EntityProto>();

  /**
   * A map from an entity's key to the entity that should be saved when
   * this transaction commits. If the value is null, the entity should
   * be deleted.
   */
  private final Map<ByteString, OnestoreEntity.EntityProto> updates =
      new HashMap<ByteString, OnestoreEntity.EntityProto>();

  private final boolean isXG;

  TransactionBuilder(boolean isXG) {
    this.isXG = isXG;
  }

  public boolean isXG() {
    return isXG;
  }

  /**
   * Returns true if we've cached the presence or absence of this entity.
   */
  public boolean isCachedEntity(OnestoreEntity.Reference key) {
    return getCache.containsKey(key.toByteString());
  }

  /**
   * Saves the original value of an entity (as returned by the datastore)
   * to the local cache.
   */
  public void addEntityToCache(OnestoreEntity.EntityProto entityPb) {
    ByteString key = entityPb.getKey().toByteString();
    if (getCache.containsKey(key)) {
      throw new IllegalStateException("shouldn't load the same entity twice within a transaction");
    }
    getCache.put(key, entityPb);
  }

  /**
   * Caches the absence of an entity (according to the datastore).
   */
  public void addEntityAbsenceToCache(OnestoreEntity.Reference key) {
    ByteString keyBytes = key.toByteString();
    if (getCache.containsKey(keyBytes)) {
      throw new IllegalStateException("shouldn't load the same entity twice within a transaction");
    }
    getCache.put(keyBytes, (OnestoreEntity.EntityProto) null);
  }

  /**
   * Returns a cached entity, or null if the entity's absence was cached.
   */
  @Nullable
  public OnestoreEntity.EntityProto getCachedEntity(OnestoreEntity.Reference key) {
    ByteString keyBytes = key.toByteString();
    if (!getCache.containsKey(keyBytes)) {
      throw new IllegalStateException("entity's status unexpectedly not in cache");
    }
    return getCache.get(keyBytes);
  }

  /**
   * Update transaction with result from a TransactionQuery call.
   */
  public DatastoreV3Pb.QueryResult handleQueryResult(byte[] resultBytes) {
    RemoteApiPb.TransactionQueryResult result = new RemoteApiPb.TransactionQueryResult();
    result.mergeFrom(resultBytes);

    if (isCachedEntity(result.getEntityGroupKey())) {
      OnestoreEntity.EntityProto cached = getCachedEntity(result.getEntityGroupKey());
      if (!((result.hasEntityGroup() && result.getEntityGroup().equals(cached))
            || (!result.hasEntityGroup() && cached == null))) {
        throw new ConcurrentModificationException("Transaction precondition failed.");
      }
    } else if (result.hasEntityGroup()) {
      addEntityToCache(result.getEntityGroup());
    } else {
      addEntityAbsenceToCache(result.getEntityGroupKey());
    }
    return result.getResult();
  }

  public void putEntityOnCommit(OnestoreEntity.EntityProto entity) {
    updates.put(entity.getKey().toByteString(), entity);
  }

  public void deleteEntityOnCommit(OnestoreEntity.Reference key) {
    updates.put(key.toByteString(), null);
  }

  /**
   * Creates a request to perform this transaction on the server.
   */
  public RemoteApiPb.TransactionRequest makeCommitRequest() {
    RemoteApiPb.TransactionRequest result = new RemoteApiPb.TransactionRequest();
    result.setAllowMultipleEg(isXG);
    for (Map.Entry<ByteString, OnestoreEntity.EntityProto> entry : getCache.entrySet()) {
      if (entry.getValue() == null) {
        result.addPrecondition(makeEntityNotFoundPrecondition(entry.getKey()));
      } else {
        result.addPrecondition(makeEqualEntityPrecondition(entry.getValue()));
      }
    }
    for (Map.Entry<ByteString, OnestoreEntity.EntityProto> entry : updates.entrySet()) {
      OnestoreEntity.EntityProto entityPb = entry.getValue();
      if (entityPb == null) {
        result.getMutableDeletes().addKey().mergeFrom(entry.getKey().toByteArray());
      } else {
        result.getMutablePuts().addEntity(entityPb);
      }
    }
    return result;
  }

  private static RemoteApiPb.TransactionRequest.Precondition makeEntityNotFoundPrecondition(
      ByteString key) {
    OnestoreEntity.Reference ref = new OnestoreEntity.Reference();
    ref.mergeFrom(key.toByteArray());

    RemoteApiPb.TransactionRequest.Precondition result =
        new RemoteApiPb.TransactionRequest.Precondition();
    result.setKey(ref);
    return result;
  }

  private static RemoteApiPb.TransactionRequest.Precondition makeEqualEntityPrecondition(
      OnestoreEntity.EntityProto entityPb) {
    RemoteApiPb.TransactionRequest.Precondition result =
        new RemoteApiPb.TransactionRequest.Precondition();
    result.setKey(entityPb.getKey());
    result.setHashAsBytes(computeSha1(entityPb));
    return result;
  }

  private static byte[] computeSha1(OnestoreEntity.EntityProto entity) {
    MessageDigest md;
    try {
      md = MessageDigest.getInstance("SHA-1");
    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException("can't compute sha1 hash");
    }
    byte[] entityBytes = entity.toByteArray();
    md.update(entityBytes, 0, entityBytes.length);
    return md.digest();
  }
}
TOP

Related Classes of com.google.appengine.tools.remoteapi.TransactionBuilder

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.