Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.google.cloud.storage.GrpcStorageImpl Maven / Gradle / Ivy
/*
* Copyright 2022 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
*
* http://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.cloud.storage;
import static com.google.cloud.storage.ByteSizeConstants._16MiB;
import static com.google.cloud.storage.ByteSizeConstants._1MiB;
import static com.google.cloud.storage.ByteSizeConstants._256KiB;
import static com.google.cloud.storage.CrossTransportUtils.fmtMethodName;
import static com.google.cloud.storage.CrossTransportUtils.throwHttpJsonOnly;
import static com.google.cloud.storage.GrpcToHttpStatusCodeTranslation.resultRetryAlgorithmToCodes;
import static com.google.cloud.storage.StorageV2ProtoUtils.bucketAclEntityOrAltEq;
import static com.google.cloud.storage.StorageV2ProtoUtils.objectAclEntityOrAltEq;
import static com.google.cloud.storage.Utils.bucketNameCodec;
import static com.google.cloud.storage.Utils.ifNonNull;
import static com.google.common.base.MoreObjects.firstNonNull;
import static java.util.Objects.requireNonNull;
import com.google.api.core.ApiFuture;
import com.google.api.core.ApiFutures;
import com.google.api.core.BetaApi;
import com.google.api.gax.grpc.GrpcCallContext;
import com.google.api.gax.paging.AbstractPage;
import com.google.api.gax.paging.Page;
import com.google.api.gax.retrying.ResultRetryAlgorithm;
import com.google.api.gax.rpc.ApiException;
import com.google.api.gax.rpc.ApiExceptions;
import com.google.api.gax.rpc.ClientStreamingCallable;
import com.google.api.gax.rpc.NotFoundException;
import com.google.api.gax.rpc.StatusCode;
import com.google.api.gax.rpc.UnaryCallable;
import com.google.cloud.BaseService;
import com.google.cloud.Policy;
import com.google.cloud.WriteChannel;
import com.google.cloud.storage.Acl.Entity;
import com.google.cloud.storage.BlobWriteSessionConfig.WriterFactory;
import com.google.cloud.storage.BufferedWritableByteChannelSession.BufferedWritableByteChannel;
import com.google.cloud.storage.Conversions.Decoder;
import com.google.cloud.storage.HmacKey.HmacKeyMetadata;
import com.google.cloud.storage.HmacKey.HmacKeyState;
import com.google.cloud.storage.PostPolicyV4.PostConditionsV4;
import com.google.cloud.storage.PostPolicyV4.PostFieldsV4;
import com.google.cloud.storage.Storage.ComposeRequest.SourceBlob;
import com.google.cloud.storage.UnbufferedReadableByteChannelSession.UnbufferedReadableByteChannel;
import com.google.cloud.storage.UnbufferedWritableByteChannelSession.UnbufferedWritableByteChannel;
import com.google.cloud.storage.UnifiedOpts.BucketListOpt;
import com.google.cloud.storage.UnifiedOpts.BucketSourceOpt;
import com.google.cloud.storage.UnifiedOpts.BucketTargetOpt;
import com.google.cloud.storage.UnifiedOpts.Fields;
import com.google.cloud.storage.UnifiedOpts.Mapper;
import com.google.cloud.storage.UnifiedOpts.NamedField;
import com.google.cloud.storage.UnifiedOpts.ObjectListOpt;
import com.google.cloud.storage.UnifiedOpts.ObjectSourceOpt;
import com.google.cloud.storage.UnifiedOpts.ObjectTargetOpt;
import com.google.cloud.storage.UnifiedOpts.Opts;
import com.google.cloud.storage.UnifiedOpts.ProjectId;
import com.google.cloud.storage.UnifiedOpts.UserProject;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.common.io.ByteStreams;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.iam.v1.GetIamPolicyRequest;
import com.google.iam.v1.SetIamPolicyRequest;
import com.google.iam.v1.TestIamPermissionsRequest;
import com.google.storage.v2.BidiWriteObjectRequest;
import com.google.storage.v2.BucketAccessControl;
import com.google.storage.v2.ComposeObjectRequest;
import com.google.storage.v2.ComposeObjectRequest.SourceObject;
import com.google.storage.v2.CreateBucketRequest;
import com.google.storage.v2.DeleteBucketRequest;
import com.google.storage.v2.DeleteObjectRequest;
import com.google.storage.v2.GetBucketRequest;
import com.google.storage.v2.GetObjectRequest;
import com.google.storage.v2.ListBucketsRequest;
import com.google.storage.v2.ListObjectsRequest;
import com.google.storage.v2.ListObjectsResponse;
import com.google.storage.v2.LockBucketRetentionPolicyRequest;
import com.google.storage.v2.Object;
import com.google.storage.v2.ObjectAccessControl;
import com.google.storage.v2.ReadObjectRequest;
import com.google.storage.v2.RestoreObjectRequest;
import com.google.storage.v2.RewriteObjectRequest;
import com.google.storage.v2.RewriteResponse;
import com.google.storage.v2.StorageClient;
import com.google.storage.v2.UpdateBucketRequest;
import com.google.storage.v2.UpdateObjectRequest;
import com.google.storage.v2.WriteObjectRequest;
import com.google.storage.v2.WriteObjectResponse;
import com.google.storage.v2.WriteObjectSpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators.AbstractSpliterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@BetaApi
final class GrpcStorageImpl extends BaseService
implements Storage, StorageInternal {
private static final byte[] ZERO_BYTES = new byte[0];
private static final Set READ_OPS = ImmutableSet.of(StandardOpenOption.READ);
private static final Set WRITE_OPS =
ImmutableSet.of(
StandardOpenOption.WRITE,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING);
private static final BucketSourceOption[] EMPTY_BUCKET_SOURCE_OPTIONS = new BucketSourceOption[0];
private static final Opts ALL_BLOB_FIELDS =
Opts.from(UnifiedOpts.fields(ImmutableSet.copyOf(BlobField.values())));
private static final Opts ALL_BUCKET_FIELDS =
// todo: b/308194853
Opts.from(
UnifiedOpts.fields(
Arrays.stream(BucketField.values())
.filter(f -> !f.equals(BucketField.OBJECT_RETENTION))
.collect(ImmutableSet.toImmutableSet())));
final StorageClient storageClient;
final ResponseContentLifecycleManager responseContentLifecycleManager;
final WriterFactory writerFactory;
final GrpcConversions codecs;
final GrpcRetryAlgorithmManager retryAlgorithmManager;
final SyntaxDecoders syntaxDecoders;
// workaround for https://github.com/googleapis/java-storage/issues/1736
private final Opts defaultOpts;
@Deprecated private final ProjectId defaultProjectId;
GrpcStorageImpl(
GrpcStorageOptions options,
StorageClient storageClient,
ResponseContentLifecycleManager responseContentLifecycleManager,
WriterFactory writerFactory,
Opts defaultOpts) {
super(options);
this.storageClient = storageClient;
this.responseContentLifecycleManager = responseContentLifecycleManager;
this.writerFactory = writerFactory;
this.defaultOpts = defaultOpts;
this.codecs = Conversions.grpc();
this.retryAlgorithmManager = options.getRetryAlgorithmManager();
this.syntaxDecoders = new SyntaxDecoders();
this.defaultProjectId = UnifiedOpts.projectId(options.getProjectId());
}
@Override
public void close() throws Exception {
try (StorageClient s = storageClient) {
s.shutdownNow();
org.threeten.bp.Duration terminationAwaitDuration =
getOptions().getTerminationAwaitDuration();
s.awaitTermination(terminationAwaitDuration.toMillis(), TimeUnit.MILLISECONDS);
}
}
@Override
public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) {
Opts opts = Opts.unwrap(options).resolveFrom(bucketInfo).prepend(defaultOpts);
GrpcCallContext grpcCallContext =
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
if (bucketInfo.getProject() == null || bucketInfo.getProject().trim().isEmpty()) {
bucketInfo = bucketInfo.toBuilder().setProject(getOptions().getProjectId()).build();
}
com.google.storage.v2.Bucket bucket = codecs.bucketInfo().encode(bucketInfo);
CreateBucketRequest.Builder builder =
CreateBucketRequest.newBuilder()
.setBucket(bucket)
.setBucketId(bucketInfo.getName())
.setParent("projects/_");
CreateBucketRequest req = opts.createBucketsRequest().apply(builder).build();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.createBucketCallable().call(req, merge),
syntaxDecoders.bucket);
}
@Override
public Blob create(BlobInfo blobInfo, BlobTargetOption... options) {
return create(blobInfo, null, options);
}
@Override
public Blob create(BlobInfo blobInfo, byte[] content, BlobTargetOption... options) {
content = firstNonNull(content, ZERO_BYTES);
return create(blobInfo, content, 0, content.length, options);
}
@Override
public Blob create(
BlobInfo blobInfo, byte[] content, int offset, int length, BlobTargetOption... options) {
Opts opts = Opts.unwrap(options).resolveFrom(blobInfo);
return internalDirectUpload(blobInfo, opts, ByteBuffer.wrap(content, offset, length))
.asBlob(this);
}
@Override
public Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options) {
try {
requireNonNull(blobInfo, "blobInfo must be non null");
InputStream inputStreamParam = firstNonNull(content, new ByteArrayInputStream(ZERO_BYTES));
Opts optsWithDefaults = Opts.unwrap(options).prepend(defaultOpts);
GrpcCallContext grpcCallContext =
optsWithDefaults.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
WriteObjectRequest req = getWriteObjectRequest(blobInfo, optsWithDefaults);
Hasher hasher = Hasher.enabled();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
UnbufferedWritableByteChannelSession session =
ResumableMedia.gapic()
.write()
.byteChannel(storageClient.writeObjectCallable().withDefaultCallContext(merge))
.setByteStringStrategy(ByteStringStrategy.noCopy())
.setHasher(hasher)
.direct()
.unbuffered()
.setRequest(req)
.build();
try (UnbufferedWritableByteChannel c = session.open()) {
ByteStreams.copy(Channels.newChannel(inputStreamParam), c);
}
ApiFuture responseApiFuture = session.getResult();
return this.getBlob(responseApiFuture);
} catch (IOException | ApiException e) {
throw StorageException.coalesce(e);
}
}
@Override
public Blob createFrom(BlobInfo blobInfo, Path path, BlobWriteOption... options)
throws IOException {
return createFrom(blobInfo, path, _16MiB, options);
}
@Override
public Blob createFrom(BlobInfo blobInfo, Path path, int bufferSize, BlobWriteOption... options)
throws IOException {
Opts opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts);
return internalCreateFrom(path, blobInfo, opts);
}
@Override
public Blob internalCreateFrom(Path path, BlobInfo info, Opts opts)
throws IOException {
requireNonNull(path, "path must be non null");
if (Files.isDirectory(path)) {
throw new StorageException(0, path + " is a directory");
}
GrpcCallContext grpcCallContext =
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
WriteObjectRequest req = getWriteObjectRequest(info, opts);
ClientStreamingCallable write =
storageClient.writeObjectCallable().withDefaultCallContext(grpcCallContext);
ApiFuture start = startResumableWrite(grpcCallContext, req, opts);
ApiFuture session2 =
ApiFutures.transform(
start,
rw ->
ResumableSession.grpc(
getOptions(),
retryAlgorithmManager.idempotent(),
write,
storageClient.queryWriteStatusCallable(),
rw,
Hasher.noop()),
MoreExecutors.directExecutor());
try {
GrpcResumableSession got = session2.get();
ResumableOperationResult<@Nullable Object> put = got.put(RewindableContent.of(path));
Object object = put.getObject();
if (object == null) {
// if by some odd chance the put didn't get the Object, query for it
ResumableOperationResult<@Nullable Object> query = got.query();
object = query.getObject();
}
return codecs.blobInfo().decode(object).asBlob(this);
} catch (InterruptedException | ExecutionException e) {
throw StorageException.coalesce(e);
}
}
@Override
public Blob createFrom(BlobInfo blobInfo, InputStream content, BlobWriteOption... options)
throws IOException {
return createFrom(blobInfo, content, _16MiB, options);
}
@Override
public Blob createFrom(
BlobInfo blobInfo, InputStream in, int bufferSize, BlobWriteOption... options)
throws IOException {
requireNonNull(blobInfo, "blobInfo must be non null");
Opts opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts);
GrpcCallContext grpcCallContext =
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
WriteObjectRequest req = getWriteObjectRequest(blobInfo, opts);
ApiFuture start = startResumableWrite(grpcCallContext, req, opts);
BufferedWritableByteChannelSession session =
ResumableMedia.gapic()
.write()
.byteChannel(
storageClient.writeObjectCallable().withDefaultCallContext(grpcCallContext))
.setHasher(Hasher.noop())
.setByteStringStrategy(ByteStringStrategy.noCopy())
.resumable()
.withRetryConfig(getOptions(), retryAlgorithmManager.idempotent())
.buffered(Buffers.allocateAligned(bufferSize, _256KiB))
.setStartAsync(start)
.build();
// Specifically not in the try-with, so we don't close the provided stream
ReadableByteChannel src =
Channels.newChannel(firstNonNull(in, new ByteArrayInputStream(ZERO_BYTES)));
try (BufferedWritableByteChannel dst = session.open()) {
ByteStreams.copy(src, dst);
} catch (Exception e) {
throw StorageException.coalesce(e);
}
return getBlob(session.getResult());
}
@Override
public Bucket get(String bucket, BucketGetOption... options) {
Opts unwrap = Opts.unwrap(options);
return internalBucketGet(bucket, unwrap);
}
@Override
public Bucket lockRetentionPolicy(BucketInfo bucket, BucketTargetOption... options) {
Opts opts = Opts.unwrap(options).resolveFrom(bucket).prepend(defaultOpts);
GrpcCallContext grpcCallContext =
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
LockBucketRetentionPolicyRequest.Builder builder =
LockBucketRetentionPolicyRequest.newBuilder()
.setBucket(bucketNameCodec.encode(bucket.getName()));
LockBucketRetentionPolicyRequest req =
opts.lockBucketRetentionPolicyRequest().apply(builder).build();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.lockBucketRetentionPolicyCallable().call(req, merge),
syntaxDecoders.bucket);
}
@Override
public Blob get(String bucket, String blob, BlobGetOption... options) {
return get(BlobId.of(bucket, blob), options);
}
@Override
public Blob get(BlobId blob, BlobGetOption... options) {
Opts unwrap = Opts.unwrap(options);
return internalBlobGet(blob, unwrap);
}
@Override
public Blob get(BlobId blob) {
return get(blob, new BlobGetOption[0]);
}
@Override
public Blob restore(BlobId blob, BlobRestoreOption... options) {
Opts unwrap = Opts.unwrap(options);
return internalObjectRestore(blob, unwrap);
}
private Blob internalObjectRestore(BlobId blobId, Opts opts) {
Opts finalOpts = opts.prepend(defaultOpts).prepend(ALL_BLOB_FIELDS);
GrpcCallContext grpcCallContext =
finalOpts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
RestoreObjectRequest.Builder builder =
RestoreObjectRequest.newBuilder()
.setBucket(bucketNameCodec.encode(blobId.getBucket()))
.setObject(blobId.getName());
ifNonNull(blobId.getGeneration(), builder::setGeneration);
RestoreObjectRequest req = finalOpts.restoreObjectRequest().apply(builder).build();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.restoreObjectCallable().call(req, merge),
resp -> {
BlobInfo tmp = codecs.blobInfo().decode(resp);
return finalOpts.clearBlobFields().decode(tmp).asBlob(this);
});
}
@Override
public Page list(BucketListOption... options) {
Opts opts = Opts.unwrap(options).prepend(defaultOpts).prepend(ALL_BUCKET_FIELDS);
GrpcCallContext grpcCallContext =
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
ListBucketsRequest request =
defaultProjectId
.listBuckets()
.andThen(opts.listBucketsRequest())
.apply(ListBucketsRequest.newBuilder())
.build();
try {
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(request),
() -> storageClient.listBucketsPagedCallable().call(request, merge),
resp ->
new TransformingPageDecorator<>(
resp.getPage(),
syntaxDecoders.bucket.andThen(opts.clearBucketFields()),
getOptions(),
retryAlgorithmManager.getFor(request)));
} catch (Exception e) {
throw StorageException.coalesce(e);
}
}
@Override
public Page list(String bucket, BlobListOption... options) {
Opts opts = Opts.unwrap(options).prepend(defaultOpts).prepend(ALL_BLOB_FIELDS);
GrpcCallContext grpcCallContext =
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
ListObjectsRequest.Builder builder =
ListObjectsRequest.newBuilder().setParent(bucketNameCodec.encode(bucket));
ListObjectsRequest req = opts.listObjectsRequest().apply(builder).build();
try {
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.listObjectsCallable().call(req, merge),
resp -> new ListObjectsWithSyntheticDirectoriesPage(grpcCallContext, req, resp));
} catch (Exception e) {
throw StorageException.coalesce(e);
}
}
@Override
public Bucket update(BucketInfo bucketInfo, BucketTargetOption... options) {
Opts unwrap = Opts.unwrap(options);
if (bucketInfo.getModifiedFields().isEmpty()) {
return internalBucketGet(bucketInfo.getName(), unwrap.constrainTo(BucketSourceOpt.class));
}
Opts opts = unwrap.resolveFrom(bucketInfo).prepend(defaultOpts);
GrpcCallContext grpcCallContext =
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
com.google.storage.v2.Bucket bucket = codecs.bucketInfo().encode(bucketInfo);
UpdateBucketRequest.Builder builder =
opts.updateBucketsRequest().apply(UpdateBucketRequest.newBuilder().setBucket(bucket));
builder
.getUpdateMaskBuilder()
.addAllPaths(
bucketInfo.getModifiedFields().stream()
.map(NamedField::getGrpcName)
.collect(ImmutableList.toImmutableList()));
UpdateBucketRequest req = builder.build();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.updateBucketCallable().call(req, merge),
syntaxDecoders.bucket);
}
@Override
public Blob update(BlobInfo blobInfo, BlobTargetOption... options) {
Opts unwrap = Opts.unwrap(options);
if (blobInfo.getModifiedFields().isEmpty()) {
return internalBlobGet(blobInfo.getBlobId(), unwrap.constrainTo(ObjectSourceOpt.class));
}
Opts opts = unwrap.resolveFrom(blobInfo).prepend(defaultOpts);
GrpcCallContext grpcCallContext =
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
Object object = codecs.blobInfo().encode(blobInfo);
UpdateObjectRequest.Builder builder =
opts.updateObjectsRequest().apply(UpdateObjectRequest.newBuilder().setObject(object));
builder
.getUpdateMaskBuilder()
.addAllPaths(
blobInfo.getModifiedFields().stream()
.map(NamedField::getGrpcName)
.collect(ImmutableList.toImmutableList()));
UpdateObjectRequest req = builder.build();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.updateObjectCallable().call(req, merge),
syntaxDecoders.blob);
}
@Override
public Blob update(BlobInfo blobInfo) {
return update(blobInfo, new BlobTargetOption[0]);
}
@Override
public boolean delete(String bucket, BucketSourceOption... options) {
Opts opts = Opts.unwrap(options).prepend(defaultOpts);
GrpcCallContext grpcCallContext =
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
DeleteBucketRequest.Builder builder =
DeleteBucketRequest.newBuilder().setName(bucketNameCodec.encode(bucket));
DeleteBucketRequest req = opts.deleteBucketsRequest().apply(builder).build();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Boolean.TRUE.equals(
Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> {
try {
storageClient.deleteBucketCallable().call(req, merge);
return true;
} catch (NotFoundException e) {
return false;
}
},
Decoder.identity()));
}
@Override
public boolean delete(String bucket, String blob, BlobSourceOption... options) {
return delete(BlobId.of(bucket, blob), options);
}
@Override
public boolean delete(BlobId blob, BlobSourceOption... options) {
Opts opts = Opts.unwrap(options);
try {
internalObjectDelete(blob, opts);
return true;
} catch (NotFoundException e) {
return false;
} catch (StorageException e) {
if (e.getCode() == 404) {
return false;
}
throw e;
}
}
@Override
public boolean delete(BlobId blob) {
return delete(blob, new BlobSourceOption[0]);
}
@Override
public Void internalObjectDelete(BlobId id, Opts opts) {
Opts finalOpts = opts.resolveFrom(id).prepend(defaultOpts);
GrpcCallContext grpcCallContext =
finalOpts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
DeleteObjectRequest.Builder builder =
DeleteObjectRequest.newBuilder()
.setBucket(bucketNameCodec.encode(id.getBucket()))
.setObject(id.getName());
ifNonNull(id.getGeneration(), builder::setGeneration);
DeleteObjectRequest req = finalOpts.deleteObjectsRequest().apply(builder).build();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> {
storageClient.deleteObjectCallable().call(req, merge);
return null;
},
Decoder.identity());
}
@Override
public Blob compose(ComposeRequest composeRequest) {
Opts opts = composeRequest.getTargetOpts().prepend(defaultOpts);
GrpcCallContext grpcCallContext =
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
ComposeObjectRequest.Builder builder = ComposeObjectRequest.newBuilder();
composeRequest.getSourceBlobs().stream()
.map(src -> sourceObjectEncode(src))
.forEach(builder::addSourceObjects);
final Object target = codecs.blobInfo().encode(composeRequest.getTarget());
builder.setDestination(target);
ComposeObjectRequest req = opts.composeObjectsRequest().apply(builder).build();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.composeObjectCallable().call(req, merge),
syntaxDecoders.blob);
}
@Override
public CopyWriter copy(CopyRequest copyRequest) {
BlobId src = copyRequest.getSource();
BlobInfo dst = copyRequest.getTarget();
Opts srcOpts =
Opts.unwrap(copyRequest.getSourceOptions())
.projectAsSource()
.resolveFrom(src)
.prepend(defaultOpts);
Opts dstOpts =
Opts.unwrap(copyRequest.getTargetOptions()).resolveFrom(dst).prepend(defaultOpts);
Mapper mapper =
srcOpts.rewriteObjectsRequest().andThen(dstOpts.rewriteObjectsRequest());
Object srcProto = codecs.blobId().encode(src);
Object dstProto = codecs.blobInfo().encode(dst);
RewriteObjectRequest.Builder b =
RewriteObjectRequest.newBuilder()
.setDestinationName(dstProto.getName())
.setDestinationBucket(dstProto.getBucket())
// destination_kms_key comes from dstOpts
// according to the docs in the protos, it is illegal to populate the following fields,
// clear them out if they are set
// destination_predefined_acl comes from dstOpts
// if_*_match come from srcOpts and dstOpts
// copy_source_encryption_* come from srcOpts
// common_object_request_params come from dstOpts
.setDestination(dstProto.toBuilder().clearName().clearBucket().clearKmsKey().build())
.setSourceBucket(srcProto.getBucket())
.setSourceObject(srcProto.getName());
if (src.getGeneration() != null) {
b.setSourceGeneration(src.getGeneration());
}
if (copyRequest.getMegabytesCopiedPerChunk() != null) {
b.setMaxBytesRewrittenPerCall(copyRequest.getMegabytesCopiedPerChunk() * _1MiB);
}
RewriteObjectRequest req = mapper.apply(b).build();
GrpcCallContext grpcCallContext =
srcOpts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
UnaryCallable callable =
storageClient.rewriteObjectCallable().withDefaultCallContext(grpcCallContext);
GrpcCallContext retryContext = Retrying.newCallContext();
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> callable.call(req, retryContext),
(resp) -> new GapicCopyWriter(this, callable, retryAlgorithmManager.idempotent(), resp));
}
@Override
public byte[] readAllBytes(String bucket, String blob, BlobSourceOption... options) {
return readAllBytes(BlobId.of(bucket, blob), options);
}
@Override
public byte[] readAllBytes(BlobId blob, BlobSourceOption... options) {
UnbufferedReadableByteChannelSession session = unbufferedReadSession(blob, options);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (UnbufferedReadableByteChannel r = session.open();
WritableByteChannel w = Channels.newChannel(baos)) {
ByteStreams.copy(r, w);
} catch (ApiException | IOException e) {
throw StorageException.coalesce(e);
}
return baos.toByteArray();
}
@Override
public StorageBatch batch() {
return throwHttpJsonOnly("batch()");
}
@Override
public GrpcBlobReadChannel reader(String bucket, String blob, BlobSourceOption... options) {
return reader(BlobId.of(bucket, blob), options);
}
@Override
public GrpcBlobReadChannel reader(BlobId blob, BlobSourceOption... options) {
Opts opts = Opts.unwrap(options).resolveFrom(blob).prepend(defaultOpts);
ReadObjectRequest request = getReadObjectRequest(blob, opts);
Set codes = resultRetryAlgorithmToCodes(retryAlgorithmManager.getFor(request));
GrpcCallContext grpcCallContext = Retrying.newCallContext().withRetryableCodes(codes);
return new GrpcBlobReadChannel(
storageClient.readObjectCallable().withDefaultCallContext(grpcCallContext),
getOptions(),
retryAlgorithmManager.getFor(request),
responseContentLifecycleManager,
request,
!opts.autoGzipDecompression());
}
@Override
public void downloadTo(BlobId blob, Path path, BlobSourceOption... options) {
UnbufferedReadableByteChannelSession session = unbufferedReadSession(blob, options);
try (UnbufferedReadableByteChannel r = session.open();
WritableByteChannel w = Files.newByteChannel(path, WRITE_OPS)) {
ByteStreams.copy(r, w);
} catch (ApiException | IOException e) {
throw StorageException.coalesce(e);
}
}
@Override
public void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption... options) {
UnbufferedReadableByteChannelSession session = unbufferedReadSession(blob, options);
try (UnbufferedReadableByteChannel r = session.open();
WritableByteChannel w = Channels.newChannel(outputStream)) {
ByteStreams.copy(r, w);
} catch (ApiException | IOException e) {
throw StorageException.coalesce(e);
}
}
@Override
public GrpcBlobWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options) {
Opts opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts);
GrpcCallContext grpcCallContext =
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
WriteObjectRequest req = getWriteObjectRequest(blobInfo, opts);
Hasher hasher = Hasher.noop();
// in JSON, the starting of the resumable session happens before the invocation of write can
// happen. Emulate the same thing here.
// 1. create the future
ApiFuture startResumableWrite = startResumableWrite(grpcCallContext, req, opts);
// 2. await the result of the future
ResumableWrite resumableWrite = ApiFutureUtils.await(startResumableWrite);
// 3. wrap the result in another future container before constructing the BlobWriteChannel
ApiFuture wrapped = ApiFutures.immediateFuture(resumableWrite);
return new GrpcBlobWriteChannel(
storageClient.writeObjectCallable().withDefaultCallContext(grpcCallContext),
getOptions(),
retryAlgorithmManager.idempotent(),
() -> wrapped,
hasher);
}
@Override
public BlobInfo internalDirectUpload(
BlobInfo blobInfo, Opts opts, ByteBuffer buf) {
requireNonNull(blobInfo, "blobInfo must be non null");
requireNonNull(buf, "content must be non null");
Opts optsWithDefaults = opts.prepend(defaultOpts);
GrpcCallContext grpcCallContext =
optsWithDefaults.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
WriteObjectRequest req = getWriteObjectRequest(blobInfo, optsWithDefaults);
Hasher hasher = Hasher.enabled();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
RewindableContent content = RewindableContent.of(buf);
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> {
content.rewindTo(0);
UnbufferedWritableByteChannelSession session =
ResumableMedia.gapic()
.write()
.byteChannel(storageClient.writeObjectCallable().withDefaultCallContext(merge))
.setByteStringStrategy(ByteStringStrategy.noCopy())
.setHasher(hasher)
.direct()
.unbuffered()
.setRequest(req)
.build();
try (UnbufferedWritableByteChannel c = session.open()) {
content.writeTo(c);
}
return session.getResult();
},
this::getBlob);
}
@Override
public WriteChannel writer(URL signedURL) {
return throwHttpJsonOnly(fmtMethodName("writer", URL.class));
}
@Override
public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOption... options) {
return throwHttpJsonOnly(
fmtMethodName("signUrl", BlobInfo.class, long.class, TimeUnit.class, SignUrlOption.class));
}
@Override
public PostPolicyV4 generateSignedPostPolicyV4(
BlobInfo blobInfo,
long duration,
TimeUnit unit,
PostFieldsV4 fields,
PostConditionsV4 conditions,
PostPolicyV4Option... options) {
return throwHttpJsonOnly(
fmtMethodName(
"generateSignedPostPolicyV4",
BlobInfo.class,
long.class,
TimeUnit.class,
PostFieldsV4.class,
PostConditionsV4.class,
PostPolicyV4Option.class));
}
@Override
public PostPolicyV4 generateSignedPostPolicyV4(
BlobInfo blobInfo,
long duration,
TimeUnit unit,
PostFieldsV4 fields,
PostPolicyV4Option... options) {
return throwHttpJsonOnly(
fmtMethodName(
"generateSignedPostPolicyV4",
BlobInfo.class,
long.class,
TimeUnit.class,
PostFieldsV4.class,
PostPolicyV4Option.class));
}
@Override
public PostPolicyV4 generateSignedPostPolicyV4(
BlobInfo blobInfo,
long duration,
TimeUnit unit,
PostConditionsV4 conditions,
PostPolicyV4Option... options) {
return throwHttpJsonOnly(
fmtMethodName(
"generateSignedPostPolicyV4",
BlobInfo.class,
long.class,
TimeUnit.class,
PostConditionsV4.class,
PostPolicyV4Option.class));
}
@Override
public PostPolicyV4 generateSignedPostPolicyV4(
BlobInfo blobInfo, long duration, TimeUnit unit, PostPolicyV4Option... options) {
return throwHttpJsonOnly(
fmtMethodName(
"generateSignedPostPolicyV4",
BlobInfo.class,
long.class,
TimeUnit.class,
PostPolicyV4Option.class));
}
@Override
public List get(BlobId... blobIds) {
return throwHttpJsonOnly(fmtMethodName("get", BlobId[].class));
}
@Override
public List get(Iterable blobIds) {
return throwHttpJsonOnly(fmtMethodName("get", Iterable.class));
}
@Override
public List update(BlobInfo... blobInfos) {
return throwHttpJsonOnly(fmtMethodName("update", BlobInfo[].class));
}
@Override
public List update(Iterable blobInfos) {
return throwHttpJsonOnly(fmtMethodName("update", Iterable.class));
}
@Override
public List delete(BlobId... blobIds) {
return throwHttpJsonOnly(fmtMethodName("delete", BlobId[].class));
}
@Override
public List delete(Iterable blobIds) {
return throwHttpJsonOnly(fmtMethodName("delete", Iterable.class));
}
@Override
public Acl getAcl(String bucket, Entity entity, BucketSourceOption... options) {
try {
Opts opts = Opts.unwrap(options).prepend(defaultOpts);
com.google.storage.v2.Bucket resp = getBucketWithAcls(bucket, opts);
Predicate entityPredicate =
bucketAclEntityOrAltEq(codecs.entity().encode(entity));
Optional first =
resp.getAclList().stream().filter(entityPredicate).findFirst();
// HttpStorageRpc defaults to null if Not Found
return first.map(codecs.bucketAcl()::decode).orElse(null);
} catch (NotFoundException e) {
return null;
} catch (StorageException se) {
if (se.getCode() == 404) {
return null;
} else {
throw se;
}
}
}
@Override
public Acl getAcl(String bucket, Entity entity) {
return getAcl(bucket, entity, EMPTY_BUCKET_SOURCE_OPTIONS);
}
@Override
public boolean deleteAcl(String bucket, Entity entity, BucketSourceOption... options) {
try {
Opts opts = Opts.unwrap(options).prepend(defaultOpts);
com.google.storage.v2.Bucket resp = getBucketWithAcls(bucket, opts);
String encode = codecs.entity().encode(entity);
Predicate entityPredicate = bucketAclEntityOrAltEq(encode);
List currentAcls = resp.getAclList();
ImmutableList newAcls =
currentAcls.stream()
.filter(entityPredicate.negate())
.collect(ImmutableList.toImmutableList());
if (newAcls.equals(currentAcls)) {
// we didn't actually filter anything out, no need to send an RPC, simply return false
return false;
}
long metageneration = resp.getMetageneration();
UpdateBucketRequest req = createUpdateBucketAclRequest(bucket, newAcls, metageneration);
com.google.storage.v2.Bucket updateResult = updateBucket(req);
// read the response to ensure there is no longer an acl for the specified entity
Optional first =
updateResult.getAclList().stream().filter(entityPredicate).findFirst();
return !first.isPresent();
} catch (NotFoundException e) {
// HttpStorageRpc returns false if the bucket doesn't exist :(
return false;
} catch (StorageException se) {
if (se.getCode() == 404) {
return false;
} else {
throw se;
}
}
}
@Override
public boolean deleteAcl(String bucket, Entity entity) {
return deleteAcl(bucket, entity, EMPTY_BUCKET_SOURCE_OPTIONS);
}
@Override
public Acl createAcl(String bucket, Acl acl, BucketSourceOption... options) {
return updateAcl(bucket, acl, options);
}
@Override
public Acl createAcl(String bucket, Acl acl) {
return createAcl(bucket, acl, EMPTY_BUCKET_SOURCE_OPTIONS);
}
@Override
public Acl updateAcl(String bucket, Acl acl, BucketSourceOption... options) {
try {
Opts opts = Opts.unwrap(options).prepend(defaultOpts);
com.google.storage.v2.Bucket resp = getBucketWithAcls(bucket, opts);
BucketAccessControl encode = codecs.bucketAcl().encode(acl);
String entity = encode.getEntity();
Predicate entityPredicate = bucketAclEntityOrAltEq(entity);
ImmutableList newDefaultAcls =
Streams.concat(
resp.getAclList().stream().filter(entityPredicate.negate()), Stream.of(encode))
.collect(ImmutableList.toImmutableList());
UpdateBucketRequest req =
createUpdateBucketAclRequest(bucket, newDefaultAcls, resp.getMetageneration());
com.google.storage.v2.Bucket updateResult = updateBucket(req);
Optional first =
updateResult.getAclList().stream()
.filter(entityPredicate)
.findFirst()
.map(codecs.bucketAcl()::decode);
return first.orElseThrow(
() -> new StorageException(0, "Acl update call success, but not in response"));
} catch (NotFoundException e) {
throw StorageException.coalesce(e);
}
}
@Override
public Acl updateAcl(String bucket, Acl acl) {
return updateAcl(bucket, acl, EMPTY_BUCKET_SOURCE_OPTIONS);
}
@Override
public List listAcls(String bucket, BucketSourceOption... options) {
try {
Opts opts = Opts.unwrap(options).prepend(defaultOpts);
com.google.storage.v2.Bucket resp = getBucketWithAcls(bucket, opts);
return resp.getAclList().stream()
.map(codecs.bucketAcl()::decode)
.collect(ImmutableList.toImmutableList());
} catch (NotFoundException e) {
throw StorageException.coalesce(e);
}
}
@Override
public List listAcls(String bucket) {
return listAcls(bucket, EMPTY_BUCKET_SOURCE_OPTIONS);
}
@Override
public Acl getDefaultAcl(String bucket, Entity entity) {
try {
com.google.storage.v2.Bucket resp = getBucketWithDefaultAcls(bucket);
Predicate entityPredicate =
objectAclEntityOrAltEq(codecs.entity().encode(entity));
Optional first =
resp.getDefaultObjectAclList().stream().filter(entityPredicate).findFirst();
// HttpStorageRpc defaults to null if Not Found
return first.map(codecs.objectAcl()::decode).orElse(null);
} catch (NotFoundException e) {
return null;
} catch (StorageException se) {
if (se.getCode() == 404) {
return null;
} else {
throw se;
}
}
}
@Override
public boolean deleteDefaultAcl(String bucket, Entity entity) {
try {
com.google.storage.v2.Bucket resp = getBucketWithDefaultAcls(bucket);
String encode = codecs.entity().encode(entity);
Predicate entityPredicate = objectAclEntityOrAltEq(encode);
List currentDefaultAcls = resp.getDefaultObjectAclList();
ImmutableList newDefaultAcls =
currentDefaultAcls.stream()
.filter(entityPredicate.negate())
.collect(ImmutableList.toImmutableList());
if (newDefaultAcls.equals(currentDefaultAcls)) {
// we didn't actually filter anything out, no need to send an RPC, simply return false
return false;
}
long metageneration = resp.getMetageneration();
UpdateBucketRequest req =
createUpdateDefaultAclRequest(bucket, newDefaultAcls, metageneration);
com.google.storage.v2.Bucket updateResult = updateBucket(req);
// read the response to ensure there is no longer an acl for the specified entity
Optional first =
updateResult.getDefaultObjectAclList().stream().filter(entityPredicate).findFirst();
return !first.isPresent();
} catch (NotFoundException e) {
// HttpStorageRpc returns false if the bucket doesn't exist :(
return false;
} catch (StorageException se) {
if (se.getCode() == 404) {
return false;
} else {
throw se;
}
}
}
@Override
public Acl createDefaultAcl(String bucket, Acl acl) {
return updateDefaultAcl(bucket, acl);
}
@Override
public Acl updateDefaultAcl(String bucket, Acl acl) {
try {
com.google.storage.v2.Bucket resp = getBucketWithDefaultAcls(bucket);
ObjectAccessControl encode = codecs.objectAcl().encode(acl);
String entity = encode.getEntity();
Predicate entityPredicate = objectAclEntityOrAltEq(entity);
ImmutableList newDefaultAcls =
Streams.concat(
resp.getDefaultObjectAclList().stream().filter(entityPredicate.negate()),
Stream.of(encode))
.collect(ImmutableList.toImmutableList());
UpdateBucketRequest req =
createUpdateDefaultAclRequest(bucket, newDefaultAcls, resp.getMetageneration());
com.google.storage.v2.Bucket updateResult = updateBucket(req);
Optional first =
updateResult.getDefaultObjectAclList().stream()
.filter(entityPredicate)
.findFirst()
.map(codecs.objectAcl()::decode);
return first.orElseThrow(
() -> new StorageException(0, "Acl update call success, but not in response"));
} catch (NotFoundException e) {
throw StorageException.coalesce(e);
}
}
@Override
public List listDefaultAcls(String bucket) {
try {
com.google.storage.v2.Bucket resp = getBucketWithDefaultAcls(bucket);
return resp.getDefaultObjectAclList().stream()
.map(codecs.objectAcl()::decode)
.collect(ImmutableList.toImmutableList());
} catch (NotFoundException e) {
throw StorageException.coalesce(e);
}
}
@Override
public Acl getAcl(BlobId blob, Entity entity) {
try {
Object req = codecs.blobId().encode(blob);
Object resp = getObjectWithAcls(req);
Predicate entityPredicate =
objectAclEntityOrAltEq(codecs.entity().encode(entity));
Optional first =
resp.getAclList().stream().filter(entityPredicate).findFirst();
// HttpStorageRpc defaults to null if Not Found
return first.map(codecs.objectAcl()::decode).orElse(null);
} catch (NotFoundException e) {
return null;
} catch (StorageException se) {
if (se.getCode() == 404) {
return null;
} else {
throw se;
}
}
}
@Override
public boolean deleteAcl(BlobId blob, Entity entity) {
try {
Object obj = codecs.blobId().encode(blob);
Object resp = getObjectWithAcls(obj);
String encode = codecs.entity().encode(entity);
Predicate entityPredicate = objectAclEntityOrAltEq(encode);
List currentDefaultAcls = resp.getAclList();
ImmutableList newDefaultAcls =
currentDefaultAcls.stream()
.filter(entityPredicate.negate())
.collect(ImmutableList.toImmutableList());
if (newDefaultAcls.equals(currentDefaultAcls)) {
// we didn't actually filter anything out, no need to send an RPC, simply return false
return false;
}
long metageneration = resp.getMetageneration();
UpdateObjectRequest req = createUpdateObjectAclRequest(obj, newDefaultAcls, metageneration);
Object updateResult = updateObject(req);
// read the response to ensure there is no longer an acl for the specified entity
Optional first =
updateResult.getAclList().stream().filter(entityPredicate).findFirst();
return !first.isPresent();
} catch (NotFoundException e) {
// HttpStorageRpc returns false if the bucket doesn't exist :(
return false;
} catch (StorageException se) {
if (se.getCode() == 404) {
return false;
} else {
throw se;
}
}
}
@Override
public Acl createAcl(BlobId blob, Acl acl) {
return updateAcl(blob, acl);
}
@Override
public Acl updateAcl(BlobId blob, Acl acl) {
try {
Object obj = codecs.blobId().encode(blob);
Object resp = getObjectWithAcls(obj);
ObjectAccessControl encode = codecs.objectAcl().encode(acl);
String entity = encode.getEntity();
Predicate entityPredicate = objectAclEntityOrAltEq(entity);
ImmutableList newDefaultAcls =
Streams.concat(
resp.getAclList().stream().filter(entityPredicate.negate()), Stream.of(encode))
.collect(ImmutableList.toImmutableList());
UpdateObjectRequest req =
createUpdateObjectAclRequest(obj, newDefaultAcls, resp.getMetageneration());
Object updateResult = updateObject(req);
Optional first =
updateResult.getAclList().stream()
.filter(entityPredicate)
.findFirst()
.map(codecs.objectAcl()::decode);
return first.orElseThrow(
() -> new StorageException(0, "Acl update call success, but not in response"));
} catch (NotFoundException e) {
throw StorageException.coalesce(e);
}
}
@Override
public List listAcls(BlobId blob) {
try {
Object req = codecs.blobId().encode(blob);
Object resp = getObjectWithAcls(req);
return resp.getAclList().stream()
.map(codecs.objectAcl()::decode)
.collect(ImmutableList.toImmutableList());
} catch (NotFoundException e) {
throw StorageException.coalesce(e);
}
}
@Override
public HmacKey createHmacKey(ServiceAccount serviceAccount, CreateHmacKeyOption... options) {
return CrossTransportUtils.throwHttpJsonOnly(Storage.class, "createHmacKey");
}
@Override
public Page listHmacKeys(ListHmacKeysOption... options) {
return CrossTransportUtils.throwHttpJsonOnly(Storage.class, "listHmacKey");
}
@Override
public HmacKeyMetadata getHmacKey(String accessId, GetHmacKeyOption... options) {
return CrossTransportUtils.throwHttpJsonOnly(Storage.class, "getHmacKey");
}
@Override
public void deleteHmacKey(HmacKeyMetadata hmacKeyMetadata, DeleteHmacKeyOption... options) {
CrossTransportUtils.throwHttpJsonOnly(Storage.class, "deleteHmacKey");
}
@Override
public HmacKeyMetadata updateHmacKeyState(
HmacKeyMetadata hmacKeyMetadata, HmacKeyState state, UpdateHmacKeyOption... options) {
return CrossTransportUtils.throwHttpJsonOnly(Storage.class, "updateHmacKeyState");
}
@Override
public Policy getIamPolicy(String bucket, BucketSourceOption... options) {
Opts opts = Opts.unwrap(options).prepend(defaultOpts);
GrpcCallContext grpcCallContext =
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
GetIamPolicyRequest.Builder builder =
GetIamPolicyRequest.newBuilder().setResource(bucketNameCodec.encode(bucket));
GetIamPolicyRequest req = opts.getIamPolicyRequest().apply(builder).build();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.getIamPolicyCallable().call(req, merge),
codecs.policyCodec());
}
@Override
public Policy setIamPolicy(String bucket, Policy policy, BucketSourceOption... options) {
Opts opts = Opts.unwrap(options).prepend(defaultOpts);
GrpcCallContext grpcCallContext =
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
SetIamPolicyRequest req =
SetIamPolicyRequest.newBuilder()
.setResource(bucketNameCodec.encode(bucket))
.setPolicy(codecs.policyCodec().encode(policy))
.build();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.setIamPolicyCallable().call(req, merge),
codecs.policyCodec());
}
@Override
public List testIamPermissions(
String bucket, List permissions, BucketSourceOption... options) {
Opts opts = Opts.unwrap(options).prepend(defaultOpts);
GrpcCallContext grpcCallContext =
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
TestIamPermissionsRequest req =
TestIamPermissionsRequest.newBuilder()
.setResource(bucketNameCodec.encode(bucket))
.addAllPermissions(permissions)
.build();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.testIamPermissionsCallable().call(req, merge),
resp -> {
Set heldPermissions = ImmutableSet.copyOf(resp.getPermissionsList());
return permissions.stream()
.map(heldPermissions::contains)
.collect(ImmutableList.toImmutableList());
});
}
@Override
public ServiceAccount getServiceAccount(String projectId) {
return CrossTransportUtils.throwHttpJsonOnly(Storage.class, "getServiceAccount");
}
@Override
public Notification createNotification(String bucket, NotificationInfo notificationInfo) {
return throwHttpJsonOnly(
fmtMethodName("createNotification", String.class, NotificationInfo.class));
}
@Override
public Notification getNotification(String bucket, String notificationId) {
return throwHttpJsonOnly(fmtMethodName("getNotification", String.class, String.class));
}
@Override
public List listNotifications(String bucket) {
return throwHttpJsonOnly(fmtMethodName("listNotifications", String.class));
}
@Override
public boolean deleteNotification(String bucket, String notificationId) {
return throwHttpJsonOnly(fmtMethodName("deleteNotification", String.class, String.class));
}
@BetaApi
@Override
public BlobWriteSession blobWriteSession(BlobInfo info, BlobWriteOption... options) {
Opts opts = Opts.unwrap(options).resolveFrom(info);
WritableByteChannelSession, BlobInfo> writableByteChannelSession =
writerFactory.writeSession(this, info, opts);
return BlobWriteSessions.of(writableByteChannelSession);
}
@Override
public GrpcStorageOptions getOptions() {
return (GrpcStorageOptions) super.getOptions();
}
boolean isClosed() {
return storageClient.isShutdown();
}
private Blob getBlob(ApiFuture result) {
try {
WriteObjectResponse response = ApiExceptions.callAndTranslateApiException(result);
return syntaxDecoders.blob.decode(response.getResource());
} catch (Exception e) {
throw StorageException.coalesce(e);
}
}
/** Bind some decoders for our "Syntax" classes to this instance of GrpcStorageImpl */
private final class SyntaxDecoders {
final Decoder blob =
o -> codecs.blobInfo().decode(o).asBlob(GrpcStorageImpl.this);
final Decoder bucket =
b -> codecs.bucketInfo().decode(b).asBucket(GrpcStorageImpl.this);
}
/**
* Today {@link com.google.cloud.storage.spi.v1.HttpStorageRpc#list(String, Map)} creates
* synthetic objects to represent {@code prefixes} ("directories") returned as part of a list
* objects response. Specifically, a StorageObject with an `isDirectory` attribute added.
*
* This approach is not sound, and presents an otherwise ephemeral piece of metadata as an
* actual piece of data. (A {@code prefix} is not actually an object, and therefor can't be
* queried for other object metadata.)
*
*
In an effort to preserve compatibility with the current public API, this class attempts to
* encapsulate the process of producing these Synthetic Directory Objects and lifting them into
* the Page.
*
*
This behavior should NOT be carried forward to any possible new API for the storage client.
*/
private final class ListObjectsWithSyntheticDirectoriesPage implements Page {
private final GrpcCallContext ctx;
private final ListObjectsRequest req;
private final ListObjectsResponse resp;
private ListObjectsWithSyntheticDirectoriesPage(
GrpcCallContext ctx, ListObjectsRequest req, ListObjectsResponse resp) {
this.ctx = ctx;
this.req = req;
this.resp = resp;
}
@Override
public boolean hasNextPage() {
return !resp.getNextPageToken().isEmpty();
}
@Override
public String getNextPageToken() {
return resp.getNextPageToken();
}
@Override
public Page getNextPage() {
ListObjectsRequest nextPageReq =
req.toBuilder().setPageToken(resp.getNextPageToken()).build();
try {
GrpcCallContext merge = Utils.merge(ctx, Retrying.newCallContext());
ListObjectsResponse nextPageResp =
Retrying.run(
GrpcStorageImpl.this.getOptions(),
retryAlgorithmManager.getFor(nextPageReq),
() -> storageClient.listObjectsCallable().call(nextPageReq, merge),
Decoder.identity());
return new ListObjectsWithSyntheticDirectoriesPage(ctx, nextPageReq, nextPageResp);
} catch (Exception e) {
throw StorageException.coalesce(e);
}
}
@Override
public Iterable iterateAll() {
// drop to our interface type to help type inference below with the stream.
Page curr = this;
Predicate> exhausted = p -> p != null && p.hasNextPage();
// Create a stream which will attempt to call getNextPage repeatedly until we meet our
// condition of exhaustion. By doing this we are able to rely on the retry logic in
// getNextPage
return () ->
streamIterate(curr, exhausted, Page::getNextPage)
.filter(Objects::nonNull)
.flatMap(p -> StreamSupport.stream(p.getValues().spliterator(), false))
.iterator();
}
@Override
public Iterable getValues() {
return () -> {
String bucketName = bucketNameCodec.decode(req.getParent());
return Streams.concat(
resp.getObjectsList().stream().map(syntaxDecoders.blob::decode),
resp.getPrefixesList().stream()
.map(
prefix ->
BlobInfo.newBuilder(bucketName, prefix)
.setSize(0L)
.setIsDirectory(true)
.build())
.map(info -> info.asBlob(GrpcStorageImpl.this)))
.iterator();
};
}
}
static final class TransformingPageDecorator<
RequestT,
ResponseT,
ResourceT,
PageT extends AbstractPage,
ModelT>
implements Page {
private final PageT page;
private final Decoder translator;
private final Retrying.RetryingDependencies deps;
private final ResultRetryAlgorithm> resultRetryAlgorithm;
TransformingPageDecorator(
PageT page,
Decoder translator,
Retrying.RetryingDependencies deps,
ResultRetryAlgorithm> resultRetryAlgorithm) {
this.page = page;
this.translator = translator;
this.deps = deps;
this.resultRetryAlgorithm = resultRetryAlgorithm;
}
@Override
public boolean hasNextPage() {
return page.hasNextPage();
}
@Override
public String getNextPageToken() {
return page.getNextPageToken();
}
@Override
public Page getNextPage() {
return new TransformingPageDecorator<>(
page.getNextPage(), translator, deps, resultRetryAlgorithm);
}
@SuppressWarnings({"Convert2MethodRef"})
@Override
public Iterable iterateAll() {
// iterateAll on AbstractPage isn't very friendly to decoration, as getNextPage isn't actually
// ever called. This means we aren't able to apply our retry wrapping there.
// Instead, what we do is create a stream which will attempt to call getNextPage repeatedly
// until we meet some condition of exhaustion. At that point we can apply our retry logic.
return () ->
streamIterate(
page,
p -> p != null && p.hasNextPage(),
prev -> {
// TODO: retry token header
// explicitly define this callable rather than using the method reference to
// prevent a javac 1.8 exception
// https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8056984
Callable c = () -> prev.getNextPage();
return Retrying.run(deps, resultRetryAlgorithm, c, Decoder.identity());
})
.filter(Objects::nonNull)
.flatMap(p -> StreamSupport.stream(p.getValues().spliterator(), false))
.map(translator::decode)
.iterator();
}
@Override
public Iterable getValues() {
return () ->
StreamSupport.stream(page.getValues().spliterator(), false)
.map(translator::decode)
.iterator();
}
}
private static Stream streamIterate(
T seed, Predicate super T> shouldComputeNext, UnaryOperator computeNext) {
requireNonNull(seed, "seed must be non null");
requireNonNull(shouldComputeNext, "shouldComputeNext must be non null");
requireNonNull(computeNext, "computeNext must be non null");
Spliterator spliterator =
new AbstractSpliterator(Long.MAX_VALUE, 0) {
T prev;
boolean started = false;
boolean done = false;
@Override
public boolean tryAdvance(Consumer super T> action) {
// if we haven't started, emit our seed and return
if (!started) {
started = true;
action.accept(seed);
prev = seed;
return true;
}
// if we've previously finished quickly return
if (done) {
return false;
}
// test whether we should try and compute the next value
if (shouldComputeNext.test(prev)) {
// compute the next value and figure out if we can use it
T next = computeNext.apply(prev);
if (next != null) {
action.accept(next);
prev = next;
return true;
}
}
// fallthrough, if we haven't taken an action by now consider the stream done and
// return
done = true;
return false;
}
};
return StreamSupport.stream(spliterator, false);
}
ReadObjectRequest getReadObjectRequest(BlobId blob, Opts opts) {
Object object = codecs.blobId().encode(blob);
ReadObjectRequest.Builder builder =
ReadObjectRequest.newBuilder().setBucket(object.getBucket()).setObject(object.getName());
long generation = object.getGeneration();
if (generation > 0) {
builder.setGeneration(generation);
}
return opts.readObjectRequest().apply(builder).build();
}
WriteObjectRequest getWriteObjectRequest(BlobInfo info, Opts opts) {
Object object = codecs.blobInfo().encode(info);
Object.Builder objectBuilder =
object
.toBuilder()
// required if the data is changing
.clearChecksums()
// trimmed to shave payload size
.clearGeneration()
.clearMetageneration()
.clearSize()
.clearCreateTime()
.clearUpdateTime();
WriteObjectSpec.Builder specBuilder = WriteObjectSpec.newBuilder().setResource(objectBuilder);
WriteObjectRequest.Builder requestBuilder =
WriteObjectRequest.newBuilder().setWriteObjectSpec(specBuilder);
return opts.writeObjectRequest().apply(requestBuilder).build();
}
BidiWriteObjectRequest getBidiWriteObjectRequest(BlobInfo info, Opts opts) {
Object object = codecs.blobInfo().encode(info);
Object.Builder objectBuilder =
object
.toBuilder()
// required if the data is changing
.clearChecksums()
// trimmed to shave payload size
.clearGeneration()
.clearMetageneration()
.clearSize()
.clearCreateTime()
.clearUpdateTime();
WriteObjectSpec.Builder specBuilder = WriteObjectSpec.newBuilder().setResource(objectBuilder);
BidiWriteObjectRequest.Builder requestBuilder =
BidiWriteObjectRequest.newBuilder().setWriteObjectSpec(specBuilder);
return opts.bidiWriteObjectRequest().apply(requestBuilder).build();
}
private UnbufferedReadableByteChannelSession unbufferedReadSession(
BlobId blob, BlobSourceOption[] options) {
Opts opts = Opts.unwrap(options).resolveFrom(blob).prepend(defaultOpts);
ReadObjectRequest readObjectRequest = getReadObjectRequest(blob, opts);
Set codes =
resultRetryAlgorithmToCodes(retryAlgorithmManager.getFor(readObjectRequest));
GrpcCallContext grpcCallContext =
opts.grpcMetadataMapper().apply(Retrying.newCallContext().withRetryableCodes(codes));
return ResumableMedia.gapic()
.read()
.byteChannel(
storageClient.readObjectCallable().withDefaultCallContext(grpcCallContext),
getOptions(),
retryAlgorithmManager.getFor(readObjectRequest),
responseContentLifecycleManager)
.setAutoGzipDecompression(!opts.autoGzipDecompression())
.unbuffered()
.setReadObjectRequest(readObjectRequest)
.build();
}
@VisibleForTesting
ApiFuture startResumableWrite(
GrpcCallContext grpcCallContext, WriteObjectRequest req, Opts opts) {
Set codes = resultRetryAlgorithmToCodes(retryAlgorithmManager.getFor(req));
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return ResumableMedia.gapic()
.write()
.resumableWrite(
storageClient
.startResumableWriteCallable()
.withDefaultCallContext(merge.withRetryableCodes(codes)),
req,
opts);
}
ApiFuture startResumableWrite(
GrpcCallContext grpcCallContext, BidiWriteObjectRequest req, Opts opts) {
Set codes = resultRetryAlgorithmToCodes(retryAlgorithmManager.getFor(req));
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return ResumableMedia.gapic()
.write()
.bidiResumableWrite(
storageClient
.startResumableWriteCallable()
.withDefaultCallContext(merge.withRetryableCodes(codes)),
req,
opts);
}
private SourceObject sourceObjectEncode(SourceBlob from) {
SourceObject.Builder to = SourceObject.newBuilder();
to.setName(from.getName());
ifNonNull(from.getGeneration(), to::setGeneration);
return to.build();
}
private com.google.storage.v2.Bucket getBucketWithDefaultAcls(String bucketName) {
Fields fields =
UnifiedOpts.fields(
ImmutableSet.of(
BucketField.ACL, // workaround for b/261771961
BucketField.DEFAULT_OBJECT_ACL,
BucketField.METAGENERATION));
GrpcCallContext grpcCallContext = GrpcCallContext.createDefault();
GetBucketRequest req =
fields
.getBucket()
.apply(GetBucketRequest.newBuilder())
.setName(bucketNameCodec.encode(bucketName))
.build();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.getBucketCallable().call(req, merge),
Decoder.identity());
}
private com.google.storage.v2.Bucket getBucketWithAcls(
String bucketName, Opts opts) {
Fields fields =
UnifiedOpts.fields(ImmutableSet.of(BucketField.ACL, BucketField.METAGENERATION));
GrpcCallContext grpcCallContext = GrpcCallContext.createDefault();
Mapper mapper = opts.getBucketsRequest().andThen(fields.getBucket());
GetBucketRequest req =
mapper
.apply(GetBucketRequest.newBuilder())
.setName(bucketNameCodec.encode(bucketName))
.build();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.getBucketCallable().call(req, merge),
Decoder.identity());
}
private com.google.storage.v2.Bucket updateBucket(UpdateBucketRequest req) {
GrpcCallContext grpcCallContext = GrpcCallContext.createDefault();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.updateBucketCallable().call(req, merge),
Decoder.identity());
}
private static UpdateBucketRequest createUpdateDefaultAclRequest(
String bucket, ImmutableList newDefaultAcls, long metageneration) {
com.google.storage.v2.Bucket update =
com.google.storage.v2.Bucket.newBuilder()
.setName(bucketNameCodec.encode(bucket))
.addAllDefaultObjectAcl(newDefaultAcls)
.build();
Opts opts =
Opts.from(
UnifiedOpts.fields(ImmutableSet.of(BucketField.DEFAULT_OBJECT_ACL)),
UnifiedOpts.metagenerationMatch(metageneration));
return opts.updateBucketsRequest()
.apply(UpdateBucketRequest.newBuilder())
.setBucket(update)
.build();
}
private static UpdateBucketRequest createUpdateBucketAclRequest(
String bucket, ImmutableList newDefaultAcls, long metageneration) {
com.google.storage.v2.Bucket update =
com.google.storage.v2.Bucket.newBuilder()
.setName(bucketNameCodec.encode(bucket))
.addAllAcl(newDefaultAcls)
.build();
Opts opts =
Opts.from(
UnifiedOpts.fields(ImmutableSet.of(BucketField.ACL)),
UnifiedOpts.metagenerationMatch(metageneration));
return opts.updateBucketsRequest()
.apply(UpdateBucketRequest.newBuilder())
.setBucket(update)
.build();
}
private Object getObjectWithAcls(Object obj) {
Fields fields =
UnifiedOpts.fields(ImmutableSet.of(BucketField.ACL, BucketField.METAGENERATION));
GrpcCallContext grpcCallContext = GrpcCallContext.createDefault();
GetObjectRequest req =
fields
.getObject()
.apply(GetObjectRequest.newBuilder())
.setBucket(obj.getBucket())
.setObject(obj.getName())
.build();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.getObjectCallable().call(req, merge),
Decoder.identity());
}
private static UpdateObjectRequest createUpdateObjectAclRequest(
Object obj, ImmutableList newAcls, long metageneration) {
Object update =
Object.newBuilder()
.setBucket(obj.getBucket())
.setName(obj.getName())
.addAllAcl(newAcls)
.build();
Opts opts =
Opts.from(
UnifiedOpts.fields(ImmutableSet.of(BlobField.ACL)),
UnifiedOpts.metagenerationMatch(metageneration));
return opts.updateObjectsRequest()
.apply(UpdateObjectRequest.newBuilder())
.setObject(update)
.build();
}
private Object updateObject(UpdateObjectRequest req) {
GrpcCallContext grpcCallContext = GrpcCallContext.createDefault();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.updateObjectCallable().call(req, merge),
Decoder.identity());
}
@NonNull
@Override
public BlobInfo internalObjectGet(BlobId blobId, Opts opts) {
Opts finalOpts = opts.prepend(defaultOpts).prepend(ALL_BLOB_FIELDS);
GrpcCallContext grpcCallContext =
finalOpts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
GetObjectRequest.Builder builder =
GetObjectRequest.newBuilder()
.setBucket(bucketNameCodec.encode(blobId.getBucket()))
.setObject(blobId.getName());
ifNonNull(blobId.getGeneration(), builder::setGeneration);
GetObjectRequest req = finalOpts.getObjectsRequest().apply(builder).build();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
//noinspection DataFlowIssue
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.getObjectCallable().call(req, merge),
resp -> {
BlobInfo tmp = codecs.blobInfo().decode(resp);
return finalOpts.clearBlobFields().decode(tmp);
});
}
@Nullable
private Blob internalBlobGet(BlobId blob, Opts unwrap) {
Opts opts = unwrap.resolveFrom(blob);
try {
return internalObjectGet(blob, opts).asBlob(this);
} catch (StorageException e) {
if (e.getCause() instanceof NotFoundException) {
return null;
} else {
throw e;
}
} catch (NotFoundException nfe) {
return null;
}
}
@Nullable
private Bucket internalBucketGet(String bucket, Opts unwrap) {
Opts opts = unwrap.prepend(defaultOpts).prepend(ALL_BUCKET_FIELDS);
GrpcCallContext grpcCallContext =
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
GetBucketRequest.Builder builder =
GetBucketRequest.newBuilder().setName(bucketNameCodec.encode(bucket));
GetBucketRequest req = opts.getBucketsRequest().apply(builder).build();
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
return Retrying.run(
getOptions(),
retryAlgorithmManager.getFor(req),
() -> storageClient.getBucketCallable().call(req, merge),
syntaxDecoders.bucket.andThen(opts.clearBucketFields()));
}
}