Skip to content

Commit 64bbb60

Browse files
authored
feat: expose BucketInfo.getProject as a BigInteger (#3119)
Expose publicly the project field which was previously internal only. This field represents the project number of the project the bucket belongs to. This field is still OUTPUT_ONLY, and as such does hot have a setter exposed for it. Fixes #3023
1 parent e1be49e commit 64bbb60

File tree

12 files changed

+124
-27
lines changed

12 files changed

+124
-27
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.io.InputStream;
3636
import java.io.ObjectInputStream;
3737
import java.io.Serializable;
38+
import java.math.BigInteger;
3839
import java.security.Key;
3940
import java.time.Duration;
4041
import java.time.OffsetDateTime;
@@ -502,7 +503,7 @@ public Builder setName(String name) {
502503
}
503504

504505
@Override
505-
Builder setProject(String project) {
506+
Builder setProject(BigInteger project) {
506507
infoBuilder.setProject(project);
507508
return this;
508509
}

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import java.io.ObjectInputStream;
4646
import java.io.ObjectOutputStream;
4747
import java.io.Serializable;
48+
import java.math.BigInteger;
4849
import java.time.Duration;
4950
import java.time.OffsetDateTime;
5051
import java.util.ArrayList;
@@ -80,7 +81,7 @@ public class BucketInfo implements Serializable {
8081

8182
private static final long serialVersionUID = 4793572058456298945L;
8283
private final String generatedId;
83-
private final String project;
84+
private final BigInteger project;
8485
private final String name;
8586
private final Acl.Entity owner;
8687
private final String selfLink;
@@ -1657,7 +1658,7 @@ public boolean isLive() {
16571658
public abstract static class Builder {
16581659
Builder() {}
16591660

1660-
abstract Builder setProject(String project);
1661+
abstract Builder setProject(BigInteger project);
16611662

16621663
/** Sets the bucket's name. */
16631664
public abstract Builder setName(String name);
@@ -1923,7 +1924,7 @@ public Builder setRetentionPeriodDuration(Duration retentionPeriod) {
19231924
static final class BuilderImpl extends Builder {
19241925

19251926
private String generatedId;
1926-
private String project;
1927+
private BigInteger project;
19271928
private String name;
19281929
private Acl.Entity owner;
19291930
private String selfLink;
@@ -2007,7 +2008,10 @@ public Builder setName(String name) {
20072008
}
20082009

20092010
@Override
2010-
Builder setProject(String project) {
2011+
Builder setProject(BigInteger project) {
2012+
if (!Objects.equals(this.project, project)) {
2013+
modifiedFields.add(BucketField.PROJECT);
2014+
}
20112015
this.project = project;
20122016
return this;
20132017
}
@@ -2637,7 +2641,8 @@ private Builder clearDeleteLifecycleRules() {
26372641
modifiedFields = builder.modifiedFields.build();
26382642
}
26392643

2640-
String getProject() {
2644+
/** The project number of the project the bucket belongs to */
2645+
public BigInteger getProject() {
26412646
return project;
26422647
}
26432648

@@ -2993,6 +2998,7 @@ public Builder toBuilder() {
29932998
public int hashCode() {
29942999
return Objects.hash(
29953000
generatedId,
3001+
project,
29963002
name,
29973003
owner,
29983004
selfLink,
@@ -3022,6 +3028,7 @@ public int hashCode() {
30223028
locationType,
30233029
objectRetention,
30243030
softDeletePolicy,
3031+
customPlacementConfig,
30253032
hierarchicalNamespace,
30263033
logging);
30273034
}
@@ -3036,6 +3043,7 @@ public boolean equals(Object o) {
30363043
}
30373044
BucketInfo that = (BucketInfo) o;
30383045
return Objects.equals(generatedId, that.generatedId)
3046+
&& Objects.equals(project, that.project)
30393047
&& Objects.equals(name, that.name)
30403048
&& Objects.equals(owner, that.owner)
30413049
&& Objects.equals(selfLink, that.selfLink)
@@ -3063,6 +3071,7 @@ public boolean equals(Object o) {
30633071
&& Objects.equals(iamConfiguration, that.iamConfiguration)
30643072
&& Objects.equals(autoclass, that.autoclass)
30653073
&& Objects.equals(locationType, that.locationType)
3074+
&& Objects.equals(customPlacementConfig, that.customPlacementConfig)
30663075
&& Objects.equals(objectRetention, that.objectRetention)
30673076
&& Objects.equals(softDeletePolicy, that.softDeletePolicy)
30683077
&& Objects.equals(hierarchicalNamespace, that.hierarchicalNamespace)

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import static com.google.cloud.storage.Utils.bucketNameCodec;
2121
import static com.google.cloud.storage.Utils.ifNonNull;
2222
import static com.google.cloud.storage.Utils.lift;
23-
import static com.google.cloud.storage.Utils.projectNameCodec;
23+
import static com.google.cloud.storage.Utils.projectNumberResourceCodec;
2424

2525
import com.google.api.pathtemplate.PathTemplate;
2626
import com.google.cloud.Binding;
@@ -204,7 +204,9 @@ Codec<Policy, com.google.iam.v1.Policy> policyCodec() {
204204

205205
private BucketInfo bucketInfoDecode(Bucket from) {
206206
BucketInfo.Builder to = new BucketInfo.BuilderImpl(bucketNameCodec.decode(from.getName()));
207-
to.setProject(projectNameCodec.decode(from.getProject()));
207+
if (!from.getProject().isEmpty()) {
208+
to.setProject(projectNumberResourceCodec.decode(from.getProject()));
209+
}
208210
to.setGeneratedId(from.getBucketId());
209211
maybeDecodeRetentionPolicy(from, to);
210212
ifNonNull(from.getLocation(), to::setLocation);
@@ -304,7 +306,7 @@ private BucketInfo bucketInfoDecode(Bucket from) {
304306
private Bucket bucketInfoEncode(BucketInfo from) {
305307
Bucket.Builder to = Bucket.newBuilder();
306308
to.setName(bucketNameCodec.encode(from.getName()));
307-
ifNonNull(from.getProject(), projectNameCodec::encode, to::setProject);
309+
ifNonNull(from.getProject(), projectNumberResourceCodec::encode, to::setProject);
308310
ifNonNull(from.getGeneratedId(), to::setBucketId);
309311
maybeEncodeRetentionPolicy(from, to);
310312
ifNonNull(from.getLocation(), to::setLocation);

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import static com.google.cloud.storage.StorageV2ProtoUtils.objectAclEntityOrAltEq;
2727
import static com.google.cloud.storage.Utils.bucketNameCodec;
2828
import static com.google.cloud.storage.Utils.ifNonNull;
29+
import static com.google.cloud.storage.Utils.projectNameCodec;
2930
import static com.google.common.base.MoreObjects.firstNonNull;
3031
import static java.util.Objects.requireNonNull;
3132

@@ -216,15 +217,15 @@ public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) {
216217
Opts<BucketTargetOpt> opts = Opts.unwrap(options).resolveFrom(bucketInfo).prepend(defaultOpts);
217218
GrpcCallContext grpcCallContext =
218219
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
219-
if (bucketInfo.getProject() == null || bucketInfo.getProject().trim().isEmpty()) {
220-
bucketInfo = bucketInfo.toBuilder().setProject(getOptions().getProjectId()).build();
221-
}
222220
com.google.storage.v2.Bucket bucket = codecs.bucketInfo().encode(bucketInfo);
223221
CreateBucketRequest.Builder builder =
224222
CreateBucketRequest.newBuilder()
225223
.setBucket(bucket)
226224
.setBucketId(bucketInfo.getName())
227225
.setParent("projects/_");
226+
if (bucketInfo.getProject() == null) {
227+
builder.getBucketBuilder().setProject(projectNameCodec.encode(getOptions().getProjectId()));
228+
}
228229
CreateBucketRequest req = opts.createBucketsRequest().apply(builder).build();
229230
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
230231
return retrier.run(

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

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import static com.google.cloud.storage.Utils.ifNonNull;
2323
import static com.google.cloud.storage.Utils.lift;
2424
import static com.google.cloud.storage.Utils.nullableDateTimeCodec;
25-
import static com.google.cloud.storage.Utils.projectNameCodec;
2625
import static com.google.common.base.MoreObjects.firstNonNull;
2726

2827
import com.google.api.client.util.Data;
@@ -90,10 +89,6 @@
9089
@InternalApi
9190
final class JsonConversions {
9291
static final JsonConversions INSTANCE = new JsonConversions();
93-
// gRPC has a Bucket.project property that apiary doesn't have yet.
94-
// when converting from gRPC to apiary or vice-versa we want to preserve this property. Until
95-
// such a time as the apiary model has a project field, we manually apply it with this name.
96-
private static final String PROJECT_ID_FIELD_NAME = "x_project";
9792

9893
private final Codec<Entity, String> entityCodec =
9994
Codec.of(this::entityEncode, this::entityDecode);
@@ -394,7 +389,7 @@ private SoftDeletePolicy softDeletePolicyDecode(Bucket.SoftDeletePolicy from) {
394389

395390
private Bucket bucketInfoEncode(BucketInfo from) {
396391
Bucket to = new Bucket();
397-
ifNonNull(from.getProject(), projectNameCodec::encode, p -> to.set(PROJECT_ID_FIELD_NAME, p));
392+
ifNonNull(from.getProject(), to::setProjectNumber);
398393
ifNonNull(from.getAcl(), toListOf(bucketAcl()::encode), to::setAcl);
399394
ifNonNull(from.getCors(), toListOf(cors()::encode), to::setCors);
400395
ifNonNull(from.getCreateTimeOffsetDateTime(), dateTimeCodec::encode, to::setTimeCreated);
@@ -477,10 +472,7 @@ private Bucket bucketInfoEncode(BucketInfo from) {
477472
@SuppressWarnings("deprecation")
478473
private BucketInfo bucketInfoDecode(com.google.api.services.storage.model.Bucket from) {
479474
BucketInfo.Builder to = new BucketInfo.BuilderImpl(from.getName());
480-
ifNonNull(
481-
from.get(PROJECT_ID_FIELD_NAME),
482-
lift(String.class::cast).andThen(projectNameCodec::decode),
483-
to::setProject);
475+
ifNonNull(from.getProjectNumber(), to::setProject);
484476
ifNonNull(from.getAcl(), toListOf(bucketAcl()::decode), to::setAcl);
485477
ifNonNull(from.getCors(), toListOf(cors()::decode), to::setCors);
486478
ifNonNull(from.getDefaultObjectAcl(), toListOf(objectAcl()::decode), to::setDefaultAcl);

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
import java.io.ObjectInputStream;
6363
import java.io.OutputStream;
6464
import java.io.Serializable;
65+
import java.math.BigInteger;
6566
import java.net.URL;
6667
import java.net.URLConnection;
6768
import java.nio.file.Path;
@@ -190,7 +191,10 @@ enum BucketField implements FieldSelector, NamedField {
190191
SOFT_DELETE_POLICY(
191192
"softDeletePolicy",
192193
"soft_delete_policy",
193-
com.google.api.services.storage.model.Bucket.SoftDeletePolicy.class);
194+
com.google.api.services.storage.model.Bucket.SoftDeletePolicy.class),
195+
196+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
197+
PROJECT("projectNumber", "project", BigInteger.class);
194198

195199
static final List<BucketField> REQUIRED_FIELDS = ImmutableList.of(NAME);
196200
private static final Map<String, BucketField> JSON_FIELD_NAME_INDEX;

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.google.cloud.storage.Conversions.Codec;
2626
import com.google.cloud.storage.UnifiedOpts.NamedField;
2727
import com.google.common.annotations.VisibleForTesting;
28+
import com.google.common.base.Preconditions;
2829
import com.google.common.collect.ImmutableList;
2930
import com.google.common.collect.ImmutableMap;
3031
import com.google.common.collect.MapDifference;
@@ -33,6 +34,7 @@
3334
import com.google.common.primitives.Ints;
3435
import com.google.storage.v2.BucketName;
3536
import com.google.storage.v2.ProjectName;
37+
import java.math.BigInteger;
3638
import java.time.Duration;
3739
import java.time.Instant;
3840
import java.time.OffsetDateTime;
@@ -174,6 +176,27 @@ final class Utils {
174176
}
175177
});
176178

179+
/**
180+
* Define a Codec which encapsulates the logic necessary to handle encoding and decoding project
181+
* numbers.
182+
*/
183+
static final Codec<@NonNull BigInteger, @NonNull String> projectNumberResourceCodec =
184+
Codec.of(
185+
projectNumber -> {
186+
requireNonNull(projectNumber, "projectNumber must be non null");
187+
return ProjectName.format(projectNumber.toString());
188+
},
189+
projectNumberResource -> {
190+
requireNonNull(projectNumberResource, "projectNumberResource must be non null");
191+
Preconditions.checkArgument(
192+
ProjectName.isParsableFrom(projectNumberResource),
193+
"projectNumberResource '%s' is not parsable as a %s",
194+
projectNumberResource,
195+
ProjectName.class.getName());
196+
ProjectName parse = ProjectName.parse(projectNumberResource);
197+
return new BigInteger(parse.getProject());
198+
});
199+
177200
static final Codec<Integer, String> crc32cCodec =
178201
Codec.of(Utils::crc32cEncode, Utils::crc32cDecode);
179202

google-cloud-storage/src/test/java/com/google/cloud/storage/GrpcUtilsTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@
1616

1717
package com.google.cloud.storage;
1818

19+
import static com.google.common.truth.Truth.assertThat;
20+
import static org.junit.Assert.assertThrows;
21+
22+
import com.google.cloud.storage.Conversions.Codec;
1923
import java.io.IOException;
24+
import java.math.BigInteger;
2025
import java.util.Collections;
26+
import org.checkerframework.checker.nullness.qual.NonNull;
2127
import org.junit.Test;
2228

2329
public final class GrpcUtilsTest {
@@ -26,4 +32,41 @@ public final class GrpcUtilsTest {
2632
public void closeAll_noNpeIfNullStream() throws IOException {
2733
GrpcUtils.closeAll(Collections.singletonList(null));
2834
}
35+
36+
@Test
37+
public void projectNumberResourceCodec_simple() {
38+
Codec<@NonNull BigInteger, @NonNull String> codec = Utils.projectNumberResourceCodec;
39+
40+
String encode = codec.encode(new BigInteger("34567892123"));
41+
assertThat(encode).isEqualTo("projects/34567892123");
42+
43+
BigInteger decode = codec.decode(encode);
44+
assertThat(decode).isEqualTo(new BigInteger("34567892123"));
45+
}
46+
47+
@Test
48+
public void projectNumberResourceCodec_decode_illegalArgumentException_whenUnParsable() {
49+
String bad = "not-a-projects/123081892932";
50+
IllegalArgumentException iae =
51+
assertThrows(
52+
IllegalArgumentException.class, () -> Utils.projectNumberResourceCodec.decode(bad));
53+
54+
assertThat(iae).hasMessageThat().contains(bad);
55+
}
56+
57+
@Test
58+
public void projectNumberResourceCodec_decode_nonNull() {
59+
assertThrows(NullPointerException.class, () -> Utils.projectNumberResourceCodec.decode(null));
60+
}
61+
62+
@Test
63+
public void projectNumberResourceCodec_encode_nonNull() {
64+
assertThrows(NullPointerException.class, () -> Utils.projectNumberResourceCodec.encode(null));
65+
}
66+
67+
@Test
68+
public void projectNumberResourceCodec_decode_notProjectNumber() {
69+
String bad = "projects/not-a-number";
70+
assertThrows(NumberFormatException.class, () -> Utils.projectNumberResourceCodec.decode(bad));
71+
}
2972
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public static final class BucketReadMaskTestParameters implements ParametersProv
9898
public ImmutableList<?> parameters() {
9999
ImmutableList<Args<BucketField, BucketInfo>> args =
100100
ImmutableList.of(
101+
new Args<>(BucketField.PROJECT, LazyAssertion.equal()),
101102
new Args<>(BucketField.ACL, LazyAssertion.equal()),
102103
new Args<>(BucketField.AUTOCLASS, LazyAssertion.equal()),
103104
new Args<>(BucketField.BILLING, LazyAssertion.equal()),

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ public void storage_BucketGetOption_fields_BucketField() {
328328
"website",
329329
"softDeletePolicy",
330330
"hierarchicalNamespace",
331-
"website");
331+
"projectNumber");
332332
s.get(
333333
b.getName(),
334334
BucketGetOption.fields(TestUtils.filterOutHttpOnlyBucketFields(BucketField.values())));
@@ -821,7 +821,7 @@ public void storage_BucketListOption_fields_BucketField() {
821821
"items/website",
822822
"items/softDeletePolicy",
823823
"items/hierarchicalNamespace",
824-
"items/website");
824+
"items/projectNumber");
825825
s.list(BucketListOption.fields(TestUtils.filterOutHttpOnlyBucketFields(BucketField.values())));
826826
requestAuditing.assertQueryParam("fields", expected, splitOnCommaToSet());
827827
}

google-cloud-storage/src/test/java/com/google/cloud/storage/jqwik/BucketArbitraryProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import static com.google.cloud.storage.PackagePrivateMethodWorkarounds.ifNonNull;
2020

21-
import com.google.cloud.storage.jqwik.StorageArbitraries.ProjectID;
21+
import com.google.cloud.storage.jqwik.StorageArbitraries.ProjectNumber;
2222
import com.google.storage.v2.Bucket;
2323
import com.google.storage.v2.BucketName;
2424
import com.google.storage.v2.ProjectName;
@@ -76,7 +76,7 @@ public Set<Arbitrary<?>> provideFor(TypeUsage targetType, SubtypeProvider subtyp
7676
StorageArbitraries.etag())
7777
.as(Tuple::of),
7878
Combinators.combine(
79-
StorageArbitraries.projectID().map(ProjectID::toProjectName),
79+
StorageArbitraries.projectNumber().map(ProjectNumber::toProjectName),
8080
StorageArbitraries
8181
.alnum() // ignored for now, tuples can't be a single element
8282
)

0 commit comments

Comments
 (0)