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

com.google.appengine.api.datastore.InternalTransactionCloudDatastoreV1 Maven / Gradle / Ivy

Go to download

API for Google App Engine standard environment with some of the dependencies shaded (repackaged)

There is a newer version: 2.0.27
Show newest version
/*
 * Copyright 2021 Google LLC
 *
 * 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
 *
 *     https://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 com.google.appengine.api.datastore;

import com.google.appengine.api.utils.FutureWrapper;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Maps;
import com.google.common.primitives.Bytes;
import com.google.datastore.v1.BeginTransactionResponse;
import com.google.datastore.v1.CommitRequest;
import com.google.datastore.v1.Mutation;
import com.google.datastore.v1.RollbackRequest;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Implementation of the Cloud Datastore v1 specific logic to handle a {@link Transaction}.
 *
 * 

In Cloud Datastore, puts and gets are stored on the client until commit. This class serializes * mutations as they are received to avoid memory penalties associated with the full proto objects. */ class InternalTransactionCloudDatastoreV1 implements TransactionImpl.InternalTransaction { /** * Prefix for transaction IDs ({@link #getId()}). Enables this class to reliably distinguish * transactions it created from v3 transactions, whose IDs are always entirely numerical. */ private static final String TXN_ID_PREFIX = "v1-"; // A blank builder for writing byte arrays. Users must clear fields after modification. private final CommitRequest.Builder commitReqBuilder = CommitRequest.newBuilder(); /** * Generates a unique identifier (for a given runtime) which can be used for later lookup of the * instance. */ private static final AtomicLong clientIdGenerator = new AtomicLong(); /** * Used to store {@link InternalTransactionCloudDatastoreV1} objects for reidentification when a * potentially wrapped Transaction object is passed back to the SDK in a future call. Each {@link * InternalTransactionCloudDatastoreV1} instance is wrapped in a {@link TransactionImpl}. We use * weak references in this static map because this object's purpose is tied to the lifetime of the * wrapper. */ private static final Map internalTransactionRegister = new MapMaker().weakValues().makeMap(); /** * The ID reported through {@link #getId()}. This ID is also used for instance lookup, see {@link * #get(Transaction)}. */ private final String clientId = TXN_ID_PREFIX + Long.toString(clientIdGenerator.getAndIncrement()); /** * The list of mutations (deferred Put/Delete operations) that will be sent to the server as part * of the Commit RPC. A linked map is used to generate consistent results for unit tests; however * iteration order shouldn't affect correctness. */ private final Map mutationMap = Maps.newLinkedHashMap(); /** * The {@link Future} associated with the BeginTransaction RPC we sent to the datastore server. */ private final Future beginTxnFuture; protected final CloudDatastoreV1Client dsApiProxy; private boolean isWritable = true; private final boolean isReadOnly; /** * Objects should be created with {@link #create(CloudDatastoreV1Client, Future)} due to * post-construction manipulation. */ private InternalTransactionCloudDatastoreV1( CloudDatastoreV1Client dsApiProxy, Future beginTxnFuture, boolean isReadOnly) { this.dsApiProxy = dsApiProxy; this.beginTxnFuture = beginTxnFuture; this.isReadOnly = isReadOnly; } static TransactionImpl.InternalTransaction create( CloudDatastoreV1Client dsApiProxy, Future future, boolean isReadOnly) { return registerTxn(new InternalTransactionCloudDatastoreV1(dsApiProxy, future, isReadOnly)); } /** Convert a mutation to a format suitable for committing later. */ // NOTE: This storage approach saves space for puts where most of the data is in the // entity properties. However, we are effectively storing the key twice, which could increase // our memory footprint during large deletes, or puts which use the key space for storage. // An alternative is to manually encode each mutation and use the segment of the serialized // bytes which correspond to the key as the map key. byte[] serializeMutation(Mutation mutation) { byte[] bytes = commitReqBuilder.addMutations(mutation).buildPartial().toByteArray(); commitReqBuilder.clearMutations(); return bytes; } /** Convert the partial proto segments into a serialized {@link CommitRequest}. */ Future sendCommit(Collection mutations) { byte[][] protoSegmentsArray = new byte[mutations.size() + 1][]; protoSegmentsArray[0] = CommitRequest.newBuilder().setTransaction(getTransactionBytes()).build().toByteArray(); int arrayIndex = 1; for (byte[] mutData : mutations) { protoSegmentsArray[arrayIndex++] = mutData; } try { return dsApiProxy.rawCommit(Bytes.concat(protoSegmentsArray)); } catch (InvalidProtocolBufferException e) { throw new RuntimeException("Unexpected error.", e); } } /** * Register a new transaction on the internal roaster. * * @return The txn, for chaining. */ static InternalTransactionCloudDatastoreV1 registerTxn(InternalTransactionCloudDatastoreV1 txn) { internalTransactionRegister.put(txn.clientId, txn); return txn; } /** * Returns the transaction bytes for this transaction. Blocks on the future since the bytes are * returned by the datastore server. */ ByteString getTransactionBytes() { return FutureHelper.quietGet(beginTxnFuture).getTransaction(); } /** Schedules a put operation for when this transaction is committed. */ void deferPut(Entity entity) { // Because keys and entities are mutable, we need to make a copy of the input, to ensure that // what we're putting is what the user originally asked us to put. Given that we have to make // a deep copy anyway, we might as well copy it into the wire format, to avoid needing yet // another copy when we commit. deferPut(DataTypeTranslator.toV1Entity(entity)); } void deferPut(com.google.datastore.v1.Entity.Builder entityProto) { checkWritable(); checkNotReadOnly(); mutationMap.put( entityProto.getKey(), serializeMutation(Mutation.newBuilder().setUpsert(entityProto).build())); } void deferDelete(Key key) { checkWritable(); checkNotReadOnly(); com.google.datastore.v1.Key keyV1 = DataTypeTranslator.toV1Key(key).build(); mutationMap.put(keyV1, serializeMutation(Mutation.newBuilder().setDelete(keyV1).build())); } @Override public Future doCommitAsync() { isWritable = false; Future result = new VoidFutureWrapper<>(sendCommit(mutationMap.values())); mutationMap.clear(); return result; } @Override public Future doRollbackAsync() { isWritable = false; mutationMap.clear(); return new VoidFutureWrapper<>( dsApiProxy.rollback( RollbackRequest.newBuilder().setTransaction(getTransactionBytes()).build())); } @Override public String getId() { return clientId; } private void checkWritable() { if (!isWritable) { throw new IllegalStateException("Transaction is not writable."); } } private void checkNotReadOnly() { if (isReadOnly) { throw new IllegalArgumentException("Attempting to write to a read-only transaction."); } } /** * Locates the {@link InternalTransactionCloudDatastoreV1} object associated with a {@link * Transaction} by looking up the ID in an static, threadsafe map. * * @throws IllegalArgumentException If a txn object is not found. * @return Internal transaction object associated with the given ID. */ static InternalTransactionCloudDatastoreV1 get(Transaction txn) { String txnId = txn.getId(); InternalTransactionCloudDatastoreV1 txnImpl = internalTransactionRegister.get(txnId); if (txnImpl == null) { throw new IllegalArgumentException("Transaction not found with ID: " + txnId); } return txnImpl; } static boolean isV1Transaction(Transaction txn) { return internalTransactionRegister.containsKey(txn.getId()); } private static class VoidFutureWrapper extends FutureWrapper { public VoidFutureWrapper(Future parent) { super(parent); } @Override protected Void wrap(T ignore) throws Exception { return null; } @Override protected Throwable convertException(Throwable cause) { return cause; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy