Skip to content

Commit ec59078

Browse files
committed
chore: add Storage#getBlobDescriptor skeleton
* Add BlobDescriptor interface * Open up ByteRangeSpec to be public * Add new Storage#getBlobDescriptor (currently throws UnsupportedOperationException) * Add new integration test ITBlobDescriptorTest that attempts to read the final 13 bytes of an object as a `byte[]`
1 parent 8a140eb commit ec59078

File tree

8 files changed

+163
-5
lines changed

8 files changed

+163
-5
lines changed

google-cloud-storage/clirr-ignored-differences.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,11 @@
120120
<method>* moveObject(*)</method>
121121
</difference>
122122

123+
<!-- Not breaking, new method has a default implementation -->
124+
<difference>
125+
<differenceType>7012</differenceType>
126+
<className>com/google/cloud/storage/Storage</className>
127+
<method>com.google.api.core.ApiFuture getBlobDescriptor(com.google.cloud.storage.BlobId, com.google.cloud.storage.Storage$BlobSourceOption[])</method>
128+
</difference>
129+
123130
</differences>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.storage;
18+
19+
import com.google.api.core.ApiFuture;
20+
21+
/** Blob Descriptor is to blob, what File Descriptor is to a file */
22+
public interface BlobDescriptor {
23+
24+
BlobInfo getBlobInfo();
25+
26+
ApiFuture<byte[]> readRangeAsBytes(ByteRangeSpec range);
27+
}

google-cloud-storage/src/main/java/com/google/cloud/storage/ByteRangeSpec.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
*/
4444
@InternalApi
4545
@ThreadSafe
46-
abstract class ByteRangeSpec implements Serializable {
46+
public abstract class ByteRangeSpec implements Serializable {
4747

4848
public static final long EFFECTIVE_INFINITY = Long.MAX_VALUE;
4949

@@ -111,19 +111,20 @@ public String toString() {
111111

112112
protected abstract MoreObjects.ToStringHelper append(MoreObjects.ToStringHelper tsh);
113113

114-
static ByteRangeSpec nullRange() {
114+
public static ByteRangeSpec nullRange() {
115115
return NullByteRangeSpec.INSTANCE;
116116
}
117117

118-
static ByteRangeSpec relativeLength(@Nullable Long beginOffset, @Nullable Long length) {
118+
public static ByteRangeSpec relativeLength(@Nullable Long beginOffset, @Nullable Long length) {
119119
return create(beginOffset, length, RelativeByteRangeSpec::new);
120120
}
121121

122-
static ByteRangeSpec explicit(@Nullable Long beginOffset, @Nullable Long endOffsetExclusive) {
122+
public static ByteRangeSpec explicit(
123+
@Nullable Long beginOffset, @Nullable Long endOffsetExclusive) {
123124
return create(beginOffset, endOffsetExclusive, LeftClosedRightOpenByteRangeSpec::new);
124125
}
125126

126-
static ByteRangeSpec explicitClosed(
127+
public static ByteRangeSpec explicitClosed(
127128
@Nullable Long beginOffset, @Nullable Long endOffsetInclusive) {
128129
return create(beginOffset, endOffsetInclusive, LeftClosedRightClosedByteRangeSpec::new);
129130
}

google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,6 +1448,12 @@ public Blob moveBlob(MoveBlobRequest request) {
14481448
syntaxDecoders.blob);
14491449
}
14501450

1451+
@Override
1452+
public ApiFuture<BlobDescriptor> getBlobDescriptor(BlobId id, BlobSourceOption... options) {
1453+
throw new UnsupportedOperationException(
1454+
fmtMethodName("getBlobDescriptor", BlobId.class, BlobSourceOption.class));
1455+
}
1456+
14511457
@Override
14521458
public GrpcStorageOptions getOptions() {
14531459
return (GrpcStorageOptions) super.getOptions();

google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static com.google.common.base.Preconditions.checkNotNull;
2323
import static java.util.Objects.requireNonNull;
2424

25+
import com.google.api.core.ApiFuture;
2526
import com.google.api.core.BetaApi;
2627
import com.google.api.core.InternalApi;
2728
import com.google.api.core.InternalExtensionOnly;
@@ -5834,4 +5835,10 @@ default BlobWriteSession blobWriteSession(BlobInfo blobInfo, BlobWriteOption...
58345835
*/
58355836
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
58365837
Blob moveBlob(MoveBlobRequest request);
5838+
5839+
@BetaApi
5840+
@TransportCompatibility({Transport.GRPC})
5841+
default ApiFuture<BlobDescriptor> getBlobDescriptor(BlobId id, BlobSourceOption... options) {
5842+
return throwGrpcOnly(fmtMethodName("getBlobDescriptor", BlobId.class, BlobSourceOption.class));
5843+
}
58375844
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.storage;
18+
19+
import static com.google.cloud.storage.TestUtils.xxd;
20+
import static com.google.common.truth.Truth.assertThat;
21+
22+
import com.google.api.core.ApiFuture;
23+
import com.google.cloud.ReadChannel;
24+
import com.google.cloud.storage.TransportCompatibility.Transport;
25+
import com.google.cloud.storage.it.runner.StorageITRunner;
26+
import com.google.cloud.storage.it.runner.annotations.Backend;
27+
import com.google.cloud.storage.it.runner.annotations.Inject;
28+
import com.google.cloud.storage.it.runner.annotations.SingleBackend;
29+
import com.google.cloud.storage.it.runner.annotations.StorageFixture;
30+
import com.google.cloud.storage.it.runner.registry.Generator;
31+
import com.google.cloud.storage.it.runner.registry.ObjectsFixture;
32+
import com.google.cloud.storage.it.runner.registry.ObjectsFixture.ObjectAndContent;
33+
import com.google.common.io.ByteStreams;
34+
import java.io.ByteArrayOutputStream;
35+
import java.io.IOException;
36+
import java.nio.channels.Channels;
37+
import java.nio.channels.WritableByteChannel;
38+
import java.util.concurrent.ExecutionException;
39+
import java.util.concurrent.TimeUnit;
40+
import java.util.concurrent.TimeoutException;
41+
import org.junit.Ignore;
42+
import org.junit.Test;
43+
import org.junit.runner.RunWith;
44+
45+
@RunWith(StorageITRunner.class)
46+
@SingleBackend(Backend.TEST_BENCH)
47+
@Ignore
48+
public final class ITBlobDescriptorTest {
49+
50+
private static final int _512KiB = 512 * 1024;
51+
52+
@Inject
53+
@StorageFixture(Transport.GRPC)
54+
public Storage storage;
55+
56+
@Inject public BucketInfo bucket;
57+
@Inject public Generator generator;
58+
@Inject public ObjectsFixture objectsFixture;
59+
60+
@Test
61+
public void bytes() throws ExecutionException, InterruptedException, TimeoutException {
62+
ObjectAndContent obj512KiB = objectsFixture.getObj512KiB();
63+
BlobInfo info = obj512KiB.getInfo();
64+
BlobId blobId = info.getBlobId();
65+
66+
BlobDescriptor blobDescriptor = storage.getBlobDescriptor(blobId).get(30, TimeUnit.SECONDS);
67+
68+
BlobInfo info1 = blobDescriptor.getBlobInfo();
69+
70+
assertThat(info1).isEqualTo(info);
71+
72+
ApiFuture<byte[]> futureRead1Bytes =
73+
blobDescriptor.readRangeAsBytes(
74+
ByteRangeSpec.explicit(_512KiB - 13L, ByteRangeSpec.EFFECTIVE_INFINITY));
75+
76+
byte[] read1Bytes = futureRead1Bytes.get(30, TimeUnit.SECONDS);
77+
byte[] expected = obj512KiB.getContent().getBytes(_512KiB - 13);
78+
assertThat(xxd(read1Bytes)).isEqualTo(xxd(expected));
79+
}
80+
81+
@Test
82+
public void readObject() throws IOException {
83+
ObjectAndContent obj512KiB = objectsFixture.getObj512KiB();
84+
BlobInfo info = obj512KiB.getInfo();
85+
BlobId blobId = info.getBlobId();
86+
87+
byte[] expected = obj512KiB.getContent().getBytes(_512KiB - 13);
88+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
89+
try (ReadChannel r = storage.reader(blobId);
90+
WritableByteChannel w = Channels.newChannel(baos)) {
91+
r.setChunkSize(0);
92+
r.seek(_512KiB - 13);
93+
ByteStreams.copy(r, w);
94+
}
95+
96+
assertThat(xxd(baos.toByteArray())).isEqualTo(xxd(expected));
97+
}
98+
}

google-cloud-storage/src/test/java/com/google/cloud/storage/it/ChecksummedTestContent.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.google.common.io.BaseEncoding;
2222
import com.google.common.primitives.Ints;
2323
import com.google.protobuf.ByteString;
24+
import com.google.protobuf.UnsafeByteOperations;
2425
import java.io.ByteArrayInputStream;
2526
import java.nio.charset.StandardCharsets;
2627
import java.util.Arrays;
@@ -42,6 +43,10 @@ public byte[] getBytes() {
4243
return bytes;
4344
}
4445

46+
public byte[] getBytes(int beginIndex) {
47+
return UnsafeByteOperations.unsafeWrap(bytes).substring(beginIndex).toByteArray();
48+
}
49+
4550
public int getCrc32c() {
4651
return crc32c;
4752
}

google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/AbstractStorageProxy.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616

1717
package com.google.cloud.storage.it.runner.registry;
1818

19+
import com.google.api.core.ApiFuture;
1920
import com.google.api.gax.paging.Page;
2021
import com.google.cloud.Policy;
2122
import com.google.cloud.ReadChannel;
2223
import com.google.cloud.WriteChannel;
2324
import com.google.cloud.storage.Acl;
2425
import com.google.cloud.storage.Acl.Entity;
2526
import com.google.cloud.storage.Blob;
27+
import com.google.cloud.storage.BlobDescriptor;
2628
import com.google.cloud.storage.BlobId;
2729
import com.google.cloud.storage.BlobInfo;
2830
import com.google.cloud.storage.BlobWriteSession;
@@ -489,6 +491,11 @@ public BlobWriteSession blobWriteSession(BlobInfo blobInfo, BlobWriteOption... o
489491
return delegate.blobWriteSession(blobInfo, options);
490492
}
491493

494+
@Override
495+
public ApiFuture<BlobDescriptor> getBlobDescriptor(BlobId id, BlobSourceOption... options) {
496+
return delegate.getBlobDescriptor(id, options);
497+
}
498+
492499
@Override
493500
public void close() throws Exception {
494501
delegate.close();

0 commit comments

Comments
 (0)