Skip to content

Commit 8074fff

Browse files
feat: add hierarchical namespace and folders features (#2445)
* feat: support includeFolders list option * Add grpc and HNS * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * fix tests * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * fix tests again * fix clirr --------- Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 736865b commit 8074fff

File tree

12 files changed

+263
-1
lines changed

12 files changed

+263
-1
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
<method>* writeAndClose(*)</method>
1616
</difference>
1717

18+
<difference>
19+
<differenceType>7013</differenceType>
20+
<className>com/google/cloud/storage/BucketInfo$Builder</className>
21+
<method>com.google.cloud.storage.BucketInfo$Builder setHierarchicalNamespace(com.google.cloud.storage.BucketInfo$HierarchicalNamespace)</method>
22+
</difference>
23+
1824
<difference>
1925
<differenceType>7013</differenceType>
2026
<className>com/google/cloud/storage/BlobInfo$Builder</className>

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,12 @@ Builder setObjectRetention(ObjectRetention objectRetention) {
748748
return this;
749749
}
750750

751+
@Override
752+
public Builder setHierarchicalNamespace(HierarchicalNamespace hierarchicalNamespace) {
753+
infoBuilder.setHierarchicalNamespace(hierarchicalNamespace);
754+
return this;
755+
}
756+
751757
@Override
752758
public Bucket build() {
753759
return new Bucket(storage, infoBuilder);

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

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ public class BucketInfo implements Serializable {
118118
private final Logging logging;
119119
private final CustomPlacementConfig customPlacementConfig;
120120
private final ObjectRetention objectRetention;
121+
private final HierarchicalNamespace hierarchicalNamespace;
121122

122123
private final transient ImmutableSet<NamedField> modifiedFields;
123124

@@ -713,6 +714,71 @@ public Logging build() {
713714
}
714715
}
715716

717+
/** The bucket's hierarchical namespace (Folders) configuration. Enable this to use HNS. */
718+
public static final class HierarchicalNamespace implements Serializable {
719+
720+
private static final long serialVersionUID = 5932926691444613101L;
721+
private Boolean enabled;
722+
723+
public Boolean getEnabled() {
724+
return enabled;
725+
}
726+
727+
@Override
728+
public boolean equals(Object o) {
729+
if (this == o) {
730+
return true;
731+
}
732+
if (!(o instanceof HierarchicalNamespace)) {
733+
return false;
734+
}
735+
HierarchicalNamespace that = (HierarchicalNamespace) o;
736+
return Objects.equals(enabled, that.enabled);
737+
}
738+
739+
@Override
740+
public int hashCode() {
741+
return Objects.hash(enabled);
742+
}
743+
744+
@Override
745+
public String toString() {
746+
return MoreObjects.toStringHelper(this).add("enabled", enabled).toString();
747+
}
748+
749+
private HierarchicalNamespace() {}
750+
751+
private HierarchicalNamespace(Builder builder) {
752+
this.enabled = builder.enabled;
753+
}
754+
755+
public static Builder newBuilder() {
756+
return new Builder();
757+
}
758+
759+
public Builder toBuilder() {
760+
return newBuilder().setEnabled(enabled);
761+
}
762+
763+
public static final class Builder {
764+
private Boolean enabled;
765+
766+
/**
767+
* Sets whether Hierarchical Namespace (Folders) is enabled for this bucket. This can only be
768+
* enabled at bucket create time. If this is enabled, Uniform Bucket-Level Access must also be
769+
* enabled.
770+
*/
771+
public Builder setEnabled(Boolean enabled) {
772+
this.enabled = enabled;
773+
return this;
774+
}
775+
776+
public HierarchicalNamespace build() {
777+
return new HierarchicalNamespace(this);
778+
}
779+
}
780+
}
781+
716782
/**
717783
* Lifecycle rule for a bucket. Allows supported Actions, such as deleting and changing storage
718784
* class, to be executed when certain Conditions are met.
@@ -1683,6 +1749,8 @@ public Builder setRetentionPeriodDuration(Duration retentionPeriod) {
16831749

16841750
public abstract Builder setCustomPlacementConfig(CustomPlacementConfig customPlacementConfig);
16851751

1752+
public abstract Builder setHierarchicalNamespace(HierarchicalNamespace hierarchicalNamespace);
1753+
16861754
abstract Builder setObjectRetention(ObjectRetention objectRetention);
16871755

16881756
/** Creates a {@code BucketInfo} object. */
@@ -1783,6 +1851,7 @@ static final class BuilderImpl extends Builder {
17831851
private Logging logging;
17841852
private CustomPlacementConfig customPlacementConfig;
17851853
private ObjectRetention objectRetention;
1854+
private HierarchicalNamespace hierarchicalNamespace;
17861855
private final ImmutableSet.Builder<NamedField> modifiedFields = ImmutableSet.builder();
17871856

17881857
BuilderImpl(String name) {
@@ -1822,6 +1891,7 @@ static final class BuilderImpl extends Builder {
18221891
logging = bucketInfo.logging;
18231892
customPlacementConfig = bucketInfo.customPlacementConfig;
18241893
objectRetention = bucketInfo.objectRetention;
1894+
hierarchicalNamespace = bucketInfo.hierarchicalNamespace;
18251895
}
18261896

18271897
@Override
@@ -2187,6 +2257,15 @@ Builder setObjectRetention(ObjectRetention objectRetention) {
21872257
return this;
21882258
}
21892259

2260+
@Override
2261+
public Builder setHierarchicalNamespace(HierarchicalNamespace hierarchicalNamespace) {
2262+
if (!Objects.equals(this.hierarchicalNamespace, hierarchicalNamespace)) {
2263+
modifiedFields.add(BucketField.HIERARCHICAL_NAMESPACE);
2264+
}
2265+
this.hierarchicalNamespace = hierarchicalNamespace;
2266+
return this;
2267+
}
2268+
21902269
@Override
21912270
Builder setLocationType(String locationType) {
21922271
if (!Objects.equals(this.locationType, locationType)) {
@@ -2428,6 +2507,7 @@ private Builder clearDeleteLifecycleRules() {
24282507
logging = builder.logging;
24292508
customPlacementConfig = builder.customPlacementConfig;
24302509
objectRetention = builder.objectRetention;
2510+
hierarchicalNamespace = builder.hierarchicalNamespace;
24312511
modifiedFields = builder.modifiedFields.build();
24322512
}
24332513

@@ -2768,6 +2848,11 @@ public ObjectRetention getObjectRetention() {
27682848
return objectRetention;
27692849
}
27702850

2851+
/** Returns the Hierarchical Namespace (Folders) Configuration */
2852+
public HierarchicalNamespace getHierarchicalNamespace() {
2853+
return hierarchicalNamespace;
2854+
}
2855+
27712856
/** Returns a builder for the current bucket. */
27722857
public Builder toBuilder() {
27732858
return new BuilderImpl(this);
@@ -2805,6 +2890,7 @@ public int hashCode() {
28052890
autoclass,
28062891
locationType,
28072892
objectRetention,
2893+
hierarchicalNamespace,
28082894
logging);
28092895
}
28102896

@@ -2846,6 +2932,7 @@ public boolean equals(Object o) {
28462932
&& Objects.equals(autoclass, that.autoclass)
28472933
&& Objects.equals(locationType, that.locationType)
28482934
&& Objects.equals(objectRetention, that.objectRetention)
2935+
&& Objects.equals(hierarchicalNamespace, that.hierarchicalNamespace)
28492936
&& Objects.equals(logging, that.logging);
28502937
}
28512938

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ final class GrpcConversions {
110110
private final Codec<Condition, Expr> iamConditionCodec =
111111
Codec.of(this::conditionEncode, this::conditionDecode);
112112

113+
private final Codec<BucketInfo.HierarchicalNamespace, Bucket.HierarchicalNamespace>
114+
hierarchicalNamespaceCodec =
115+
Codec.of(this::hierarchicalNamespaceEncode, this::hierarchicalNamespaceDecode);
116+
113117
@VisibleForTesting
114118
final Codec<OffsetDateTime, Timestamp> timestampCodec =
115119
Codec.of(
@@ -297,6 +301,10 @@ private BucketInfo bucketInfoDecode(Bucket from) {
297301
.setDataLocations(customPlacementConfig.getDataLocationsList())
298302
.build());
299303
}
304+
if (from.hasHierarchicalNamespace()) {
305+
to.setHierarchicalNamespace(
306+
hierarchicalNamespaceCodec.decode(from.getHierarchicalNamespace()));
307+
}
300308
// TODO(frankyn): Add SelfLink when the field is available
301309
if (!from.getEtag().isEmpty()) {
302310
to.setEtag(from.getEtag());
@@ -382,6 +390,10 @@ private Bucket bucketInfoEncode(BucketInfo from) {
382390
.addAllDataLocations(customPlacementConfig.getDataLocations())
383391
.build());
384392
}
393+
ifNonNull(
394+
from.getHierarchicalNamespace(),
395+
hierarchicalNamespaceCodec::encode,
396+
to::setHierarchicalNamespace);
385397
// TODO(frankyn): Add SelfLink when the field is available
386398
ifNonNull(from.getEtag(), to::setEtag);
387399
return to.build();
@@ -589,6 +601,20 @@ private Bucket.Autoclass autoclassEncode(BucketInfo.Autoclass from) {
589601
return to.build();
590602
}
591603

604+
private Bucket.HierarchicalNamespace hierarchicalNamespaceEncode(
605+
BucketInfo.HierarchicalNamespace from) {
606+
Bucket.HierarchicalNamespace.Builder to = Bucket.HierarchicalNamespace.newBuilder();
607+
ifNonNull(from.getEnabled(), to::setEnabled);
608+
return to.build();
609+
}
610+
611+
private BucketInfo.HierarchicalNamespace hierarchicalNamespaceDecode(
612+
Bucket.HierarchicalNamespace from) {
613+
BucketInfo.HierarchicalNamespace.Builder to = BucketInfo.HierarchicalNamespace.newBuilder();
614+
to.setEnabled(from.getEnabled());
615+
return to.build();
616+
}
617+
592618
private Bucket.IamConfig iamConfigEncode(BucketInfo.IamConfiguration from) {
593619
Bucket.IamConfig.Builder to = Bucket.IamConfig.newBuilder();
594620
to.setUniformBucketLevelAccess(ublaEncode(from));

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ final class JsonConversions {
137137
private final Codec<BlobInfo, StorageObject> blobInfoCodec =
138138
Codec.of(this::blobInfoEncode, this::blobInfoDecode);
139139

140+
private final Codec<BucketInfo.HierarchicalNamespace, Bucket.HierarchicalNamespace>
141+
hierarchicalNamespaceCodec =
142+
Codec.of(this::hierarchicalNamespaceEncode, this::hierarchicalNamespaceDecode);
143+
140144
private final Codec<NotificationInfo, com.google.api.services.storage.model.Notification>
141145
notificationInfoCodec = Codec.of(this::notificationEncode, this::notificationDecode);
142146
private final Codec<CustomPlacementConfig, Bucket.CustomPlacementConfig>
@@ -437,6 +441,10 @@ private Bucket bucketInfoEncode(BucketInfo from) {
437441
this::customPlacementConfigEncode,
438442
to::setCustomPlacementConfig);
439443
ifNonNull(from.getObjectRetention(), this::objectRetentionEncode, to::setObjectRetention);
444+
ifNonNull(
445+
from.getHierarchicalNamespace(),
446+
this::hierarchicalNamespaceEncode,
447+
to::setHierarchicalNamespace);
440448
return to;
441449
}
442450

@@ -487,6 +495,10 @@ private BucketInfo bucketInfoDecode(com.google.api.services.storage.model.Bucket
487495
from.getCustomPlacementConfig(),
488496
this::customPlacementConfigDecode,
489497
to::setCustomPlacementConfig);
498+
ifNonNull(
499+
from.getHierarchicalNamespace(),
500+
this::hierarchicalNamespaceDecode,
501+
to::setHierarchicalNamespace);
490502
ifNonNull(from.getObjectRetention(), this::objectRetentionDecode, to::setObjectRetention);
491503
return to.build();
492504
}
@@ -861,6 +873,20 @@ private com.google.api.services.storage.model.Notification notificationEncode(
861873
return to;
862874
}
863875

876+
private Bucket.HierarchicalNamespace hierarchicalNamespaceEncode(
877+
BucketInfo.HierarchicalNamespace from) {
878+
Bucket.HierarchicalNamespace to = new Bucket.HierarchicalNamespace();
879+
ifNonNull(from.getEnabled(), to::setEnabled);
880+
return to;
881+
}
882+
883+
private BucketInfo.HierarchicalNamespace hierarchicalNamespaceDecode(
884+
Bucket.HierarchicalNamespace from) {
885+
BucketInfo.HierarchicalNamespace.Builder to = BucketInfo.HierarchicalNamespace.newBuilder();
886+
to.setEnabled(from.getEnabled());
887+
return to.build();
888+
}
889+
864890
private NotificationInfo notificationDecode(
865891
com.google.api.services.storage.model.Notification from) {
866892
NotificationInfo.Builder builder = new NotificationInfo.BuilderImpl(from.getTopic());

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ enum BucketField implements FieldSelector, NamedField {
159159
CUSTOM_PLACEMENT_CONFIG("customPlacementConfig", "custom_placement_config"),
160160
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
161161
AUTOCLASS("autoclass"),
162+
163+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
164+
HIERARCHICAL_NAMESPACE("hierarchicalNamespace", "hierarchical_namespace"),
162165
@TransportCompatibility({Transport.HTTP})
163166
OBJECT_RETENTION("objectRetention");
164167

@@ -1788,6 +1791,14 @@ public static BlobListOption matchGlob(@NonNull String glob) {
17881791
return new BlobListOption(UnifiedOpts.matchGlob(glob));
17891792
}
17901793

1794+
/**
1795+
* Returns an option for whether to include all Folders (including empty Folders) in response.
1796+
*/
1797+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
1798+
public static BlobListOption includeFolders(boolean includeFolders) {
1799+
return new BlobListOption(UnifiedOpts.includeFoldersAsPrefixes(includeFolders));
1800+
}
1801+
17911802
/**
17921803
* Returns an option to define the billing user project. This option is required by buckets with
17931804
* `requester_pays` flag enabled to assign operation costs.

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,10 @@ static Delimiter delimiter(@NonNull String delimiter) {
370370
return new Delimiter(delimiter);
371371
}
372372

373+
static IncludeFoldersAsPrefixes includeFoldersAsPrefixes(boolean includeFoldersAsPrefixes) {
374+
return new IncludeFoldersAsPrefixes(includeFoldersAsPrefixes);
375+
}
376+
373377
@Deprecated
374378
static DetectContentType detectContentType() {
375379
return DetectContentType.INSTANCE;
@@ -636,6 +640,20 @@ public Mapper<RewriteObjectRequest.Builder> rewriteObject() {
636640
}
637641
}
638642

643+
static final class IncludeFoldersAsPrefixes extends RpcOptVal<Boolean> implements ObjectListOpt {
644+
645+
private static final long serialVersionUID = 321916692864878282L;
646+
647+
private IncludeFoldersAsPrefixes(boolean val) {
648+
super(StorageRpc.Option.INCLUDE_FOLDERS_AS_PREFIXES, val);
649+
}
650+
651+
@Override
652+
public Mapper<ListObjectsRequest.Builder> listObjects() {
653+
return b -> b.setIncludeFoldersAsPrefixes(val);
654+
}
655+
}
656+
639657
static final class Delimiter extends RpcOptVal<String> implements ObjectListOpt {
640658
private static final long serialVersionUID = -3789556789947615714L;
641659

google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ public Tuple<String, Iterable<StorageObject>> list(final String bucket, Map<Opti
459459
.setPageToken(Option.PAGE_TOKEN.getString(options))
460460
.setFields(Option.FIELDS.getString(options))
461461
.setUserProject(Option.USER_PROJECT.getString(options))
462+
.setIncludeFoldersAsPrefixes(Option.INCLUDE_FOLDERS_AS_PREFIXES.getBoolean(options))
462463
.execute();
463464
Iterable<StorageObject> storageObjects =
464465
Iterables.concat(

google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ enum Option {
7373
DETECT_CONTENT_TYPE("detectContentType"),
7474
ENABLE_OBJECT_RETENTION("enableObjectRetention"),
7575
RETURN_RAW_INPUT_STREAM("returnRawInputStream"),
76-
OVERRIDE_UNLOCKED_RETENTION("overrideUnlockedRetention");
76+
OVERRIDE_UNLOCKED_RETENTION("overrideUnlockedRetention"),
77+
INCLUDE_FOLDERS_AS_PREFIXES("includeFoldersAsPrefixes");
78+
;
7779

7880
private final String value;
7981

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
@@ -133,6 +133,7 @@ public ImmutableList<?> parameters() {
133133
new Args<>(BucketField.TIME_CREATED, LazyAssertion.equal()),
134134
new Args<>(BucketField.UPDATED, LazyAssertion.equal()),
135135
new Args<>(BucketField.VERSIONING, LazyAssertion.equal()),
136+
new Args<>(BucketField.HIERARCHICAL_NAMESPACE, LazyAssertion.equal()),
136137
new Args<>(BucketField.WEBSITE, LazyAssertion.equal()));
137138

138139
List<String> argsDefined =

0 commit comments

Comments
 (0)