Repository: googleapis/java-storage
Pull request #2258 (2023-10-16 20:00:28)

feat: add Autoclass v2.1 support


Patch:
diff --git a/README.md b/README.md
index 43babeee..0d375de4 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ If you are using Maven with [BOM][libraries-bom], add this to your pom.xml file:
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>libraries-bom</artifactId>
-      <version>26.24.0</version>
+      <version>26.25.0</version>
       <type>pom</type>
       <scope>import</scope>
     </dependency>
@@ -50,7 +50,7 @@ If you are using Maven without the BOM, add this to your dependencies:
 If you are using Gradle 5.x or later, add this to your dependencies:
 
 ```Groovy
-implementation platform('com.google.cloud:libraries-bom:26.24.0')
+implementation platform('com.google.cloud:libraries-bom:26.25.0')
 
 implementation 'com.google.cloud:google-cloud-storage'
 ```
diff --git a/google-cloud-storage-bom/pom.xml b/google-cloud-storage-bom/pom.xml
index 53426e10..7f448b7e 100644
--- a/google-cloud-storage-bom/pom.xml
+++ b/google-cloud-storage-bom/pom.xml
@@ -24,7 +24,7 @@
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>google-cloud-shared-config</artifactId>
-    <version>1.5.8</version>
+    <version>1.6.0</version>
     <relativePath/>
   </parent>
 
diff --git a/google-cloud-storage/clirr-ignored-differences.xml b/google-cloud-storage/clirr-ignored-differences.xml
index c7189a7a..b3e4b99f 100644
--- a/google-cloud-storage/clirr-ignored-differences.xml
+++ b/google-cloud-storage/clirr-ignored-differences.xml
@@ -15,4 +15,20 @@
     <method>* writeAndClose(*)</method>
   </difference>
 
+  <!-- @BetaApi members -->
+  <difference>
+    <differenceType>7009</differenceType>
+    <className>com/google/cloud/storage/ParallelCompositeUploadBlobWriteSessionConfig$PartCleanupStrategy</className>
+    <method>boolean isDeleteAllOnError()</method>
+  </difference>
+  <difference>
+    <differenceType>7009</differenceType>
+    <className>com/google/cloud/storage/ParallelCompositeUploadBlobWriteSessionConfig$PartCleanupStrategy</className>
+    <method>boolean isDeletePartsOnSuccess()</method>
+  </difference>
+  <difference>
+    <differenceType>7009</differenceType>
+    <className>com/google/cloud/storage/ParallelCompositeUploadBlobWriteSessionConfig$PartNamingStrategy</className>
+    <method>java.lang.String fmtFields(*)</method>
+  </difference>
 </differences>
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java
index 2fd275e8..7226267e 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryConversions.java
@@ -484,6 +484,11 @@ final class ApiaryConversions {
     Bucket.Autoclass to = new Bucket.Autoclass();
     ifNonNull(from.getEnabled(), to::setEnabled);
     ifNonNull(from.getToggleTime(), dateTimeCodec::encode, to::setToggleTime);
+    ifNonNull(from.getTerminalStorageClass(), StorageClass::toString, to::setTerminalStorageClass);
+    ifNonNull(
+        from.getTerminalStorageClassUpdateTime(),
+        dateTimeCodec::encode,
+        to::setTerminalStorageClassUpdateTime);
     return to;
   }
 
@@ -491,6 +496,11 @@ final class ApiaryConversions {
     Autoclass.Builder to = Autoclass.newBuilder();
     to.setEnabled(from.getEnabled());
     ifNonNull(from.getToggleTime(), dateTimeCodec::decode, to::setToggleTime);
+    ifNonNull(from.getTerminalStorageClass(), StorageClass::valueOf, to::setTerminalStorageClass);
+    ifNonNull(
+        from.getTerminalStorageClassUpdateTime(),
+        dateTimeCodec::decode,
+        to::setTerminalStorageClassUpdateTime);
     return to.build();
   }
 
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
index bb7780c0..a4d52629 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
@@ -344,11 +344,26 @@ public class BucketInfo implements Serializable {
     }
   }
 
+  /**
+   * Configuration for the Autoclass settings of a bucket.
+   *
+   * @see <a
+   *     href="https://cloud.google.com/storage/docs/autoclass">https://cloud.google.com/storage/docs/autoclass</a>
+   */
   public static final class Autoclass implements Serializable {
 
     private static final long serialVersionUID = -2378172222188072439L;
-    private Boolean enabled;
-    private OffsetDateTime toggleTime;
+    private final Boolean enabled;
+    private final OffsetDateTime toggleTime;
+    private final StorageClass terminalStorageClass;
+    private final OffsetDateTime terminalStorageClassUpdateTime;
+
+    private Autoclass(Builder builder) {
+      this.enabled = builder.enabled;
+      this.toggleTime = builder.toggleTime;
+      this.terminalStorageClass = builder.terminalStorageClass;
+      this.terminalStorageClassUpdateTime = builder.terminalStorageClassUpdateTime;
+    }
 
     public Boolean getEnabled() {
       return enabled;
@@ -358,6 +373,14 @@ public class BucketInfo implements Serializable {
       return toggleTime;
     }
 
+    public StorageClass getTerminalStorageClass() {
+      return terminalStorageClass;
+    }
+
+    public OffsetDateTime getTerminalStorageClassUpdateTime() {
+      return terminalStorageClassUpdateTime;
+    }
+
     @Override
     public boolean equals(Object o) {
       if (this == o) {
@@ -366,13 +389,18 @@ public class BucketInfo implements Serializable {
       if (!(o instanceof Autoclass)) {
         return false;
       }
-      Autoclass that = (Autoclass) o;
-      return Objects.equals(enabled, that.enabled) && Objects.equals(toggleTime, that.toggleTime);
+      Autoclass autoclass = (Autoclass) o;
+      return Objects.equals(enabled, autoclass.enabled)
+          && Objects.equals(toggleTime, autoclass.toggleTime)
+          && Objects.equals(terminalStorageClass, autoclass.terminalStorageClass)
+          && Objects.equals(
+              terminalStorageClassUpdateTime, autoclass.terminalStorageClassUpdateTime);
     }
 
     @Override
     public int hashCode() {
-      return Objects.hash(enabled, toggleTime);
+      return Objects.hash(
+          enabled, toggleTime, terminalStorageClass, terminalStorageClassUpdateTime);
     }
 
     @Override
@@ -380,27 +408,28 @@ public class BucketInfo implements Serializable {
       return MoreObjects.toStringHelper(this)
           .add("enabled", enabled)
           .add("toggleTime", toggleTime)
+          .add("terminalStorageClass", terminalStorageClass)
+          .add("terminalStorageClassUpdateTime", terminalStorageClassUpdateTime)
           .toString();
     }
 
-    private Autoclass() {}
-
-    private Autoclass(Builder builder) {
-      this.enabled = builder.enabled;
-      this.toggleTime = builder.toggleTime;
-    }
-
     public static Builder newBuilder() {
       return new Builder();
     }
 
     public Builder toBuilder() {
-      return newBuilder().setEnabled(enabled).setToggleTime(toggleTime);
+      return newBuilder()
+          .setEnabled(enabled)
+          .setToggleTime(toggleTime)
+          .setTerminalStorageClass(terminalStorageClass)
+          .setTerminalStorageClassUpdateTime(terminalStorageClassUpdateTime);
     }
 
     public static final class Builder {
       private Boolean enabled;
       private OffsetDateTime toggleTime;
+      private StorageClass terminalStorageClass;
+      private OffsetDateTime terminalStorageClassUpdateTime;
 
       /**
        * Sets whether Autoclass is enabled for this bucket. Currently, autoclass can only be enabled
@@ -421,6 +450,30 @@ public class BucketInfo implements Serializable {
         return this;
       }
 
+      /**
+       * When set to {@link StorageClass#NEARLINE}, Autoclass restricts transitions between Standard
+       * and Nearline storage classes only.
+       *
+       * <p>When set to {@link StorageClass#ARCHIVE}, Autoclass allows transitions to Coldline and
+       * Archive as well.
+       *
+       * <p>Only valid values are {@code NEARLINE} and {@code ARCHIVE}.
+       */
+      public Builder setTerminalStorageClass(StorageClass terminalStorageClass) {
+        this.terminalStorageClass = terminalStorageClass;
+        return this;
+      }
+
+      /**
+       * The time at which Autoclass terminal storage class was last updated for this bucket.
+       *
+       * <p>This is auto populated when the feature is enabled.
+       */
+      Builder setTerminalStorageClassUpdateTime(OffsetDateTime terminalStorageClassUpdateTime) {
+        this.terminalStorageClassUpdateTime = terminalStorageClassUpdateTime;
+        return this;
+      }
+
       public Autoclass build() {
         return new Autoclass(this);
       }
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java
index 38766b94..9ddf229c 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java
@@ -565,6 +565,15 @@ final class GrpcConversions {
     BucketInfo.Autoclass.Builder to = BucketInfo.Autoclass.newBuilder();
     to.setEnabled(from.getEnabled());
     ifNonNull(from.getToggleTime(), timestampCodec::decode, to::setToggleTime);
+
+    String terminalStorageClass = from.getTerminalStorageClass();
+    if (!terminalStorageClass.isEmpty()) {
+      to.setTerminalStorageClass(StorageClass.valueOf(terminalStorageClass));
+    }
+    ifNonNull(
+        from.getTerminalStorageClassUpdateTime(),
+        timestampCodec::decode,
+        to::setTerminalStorageClassUpdateTime);
     return to.build();
   }
 
@@ -572,6 +581,11 @@ final class GrpcConversions {
     Bucket.Autoclass.Builder to = Bucket.Autoclass.newBuilder();
     ifNonNull(from.getEnabled(), to::setEnabled);
     ifNonNull(from.getToggleTime(), timestampCodec::encode, to::setToggleTime);
+    ifNonNull(from.getTerminalStorageClass(), StorageClass::toString, to::setTerminalStorageClass);
+    ifNonNull(
+        from.getTerminalStorageClassUpdateTime(),
+        timestampCodec::encode,
+        to::setTerminalStorageClassUpdateTime);
     return to.build();
   }
 
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadBlobWriteSessionConfig.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadBlobWriteSessionConfig.java
index 9a964e7a..7b9c78f5 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadBlobWriteSessionConfig.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadBlobWriteSessionConfig.java
@@ -467,7 +467,7 @@ public final class ParallelCompositeUploadBlobWriteSessionConfig extends BlobWri
       return fmtFields(randomKey, nameDigest, partRange.encode());
     }
 
-    protected abstract String fmtFields(String randomKey, String nameDigest, String partRange);
+    abstract String fmtFields(String randomKey, String nameDigest, String partRange);
 
     /**
      * Default strategy in which no stable prefix is defined.
@@ -588,11 +588,11 @@ public final class ParallelCompositeUploadBlobWriteSessionConfig extends BlobWri
       this.deleteAllOnError = deleteAllOnError;
     }
 
-    public boolean isDeletePartsOnSuccess() {
+    boolean isDeletePartsOnSuccess() {
       return deletePartsOnSuccess;
     }
 
-    public boolean isDeleteAllOnError() {
+    boolean isDeleteAllOnError() {
       return deleteAllOnError;
     }
 
diff --git a/pom.xml b/pom.xml
index 37f8df30..f2161ecd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,7 +14,7 @@
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>google-cloud-shared-config</artifactId>
-    <version>1.5.8</version>
+    <version>1.6.0</version>
   </parent>
 
   <developers>
@@ -81,7 +81,7 @@
       <dependency>
         <groupId>com.google.apis</groupId>
         <artifactId>google-api-services-storage</artifactId>
-        <version>v1-rev20230922-2.0.0</version>
+        <version>v1-rev20231012-2.0.0</version>
       </dependency>
       <dependency>
         <groupId>com.google.cloud</groupId>
diff --git a/samples/native-image-sample/pom.xml b/samples/native-image-sample/pom.xml
index d57923a6..94d65fb7 100644
--- a/samples/native-image-sample/pom.xml
+++ b/samples/native-image-sample/pom.xml
@@ -29,7 +29,7 @@
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>libraries-bom</artifactId>
-        <version>26.24.0</version>
+        <version>26.25.0</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index d29bee52..662afa5b 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -31,7 +31,7 @@
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>libraries-bom</artifactId>
-        <version>26.24.0</version>
+        <version>26.25.0</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketTest.java
index 240f5d6c..f5b2b00c 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketTest.java
@@ -42,6 +42,7 @@ import com.google.cloud.storage.Storage.BucketField;
 import com.google.cloud.storage.Storage.BucketGetOption;
 import com.google.cloud.storage.Storage.BucketListOption;
 import com.google.cloud.storage.Storage.BucketTargetOption;
+import com.google.cloud.storage.StorageClass;
 import com.google.cloud.storage.TransportCompatibility.Transport;
 import com.google.cloud.storage.it.runner.StorageITRunner;
 import com.google.cloud.storage.it.runner.annotations.Backend;
@@ -58,6 +59,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.stream.StreamSupport;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -417,6 +419,51 @@ public class ITBucketTest {
     }
   }
 
+  @Test
+  public void testCreateBucketWithAutoclass_ARCHIVE() throws Exception {
+    String bucketName = generator.randomBucketName();
+    Autoclass autoclass =
+        Autoclass.newBuilder()
+            .setEnabled(true)
+            .setTerminalStorageClass(StorageClass.ARCHIVE)
+            .build();
+    BucketInfo info = BucketInfo.newBuilder(bucketName).setAutoclass(autoclass).build();
+    try (TemporaryBucket tmpb =
+        TemporaryBucket.newBuilder().setStorage(storage).setBucketInfo(info).build()) {
+      BucketInfo remoteBucket = tmpb.getBucket();
+
+      Autoclass remoteBucketAutoclass = remoteBucket.getAutoclass();
+      assertThat(remoteBucketAutoclass).isNotNull();
+      assertThat(remoteBucketAutoclass.getEnabled()).isTrue();
+      assertThat(remoteBucketAutoclass.getToggleTime()).isNotNull();
+      assertThat(remoteBucketAutoclass.getTerminalStorageClassUpdateTime()).isNotNull();
+      assertThat(remoteBucketAutoclass.getTerminalStorageClass()).isEqualTo(StorageClass.ARCHIVE);
+
+      Page<Bucket> bucketPage = storage.list(BucketListOption.prefix(bucketName));
+      ImmutableList<Bucket> buckets = ImmutableList.copyOf(bucketPage.iterateAll());
+
+      Optional<Bucket> first =
+          buckets.stream().filter(b -> bucketName.equals(b.getName())).findFirst();
+
+      assertThat(first.isPresent()).isTrue();
+      assertThat(first.get().getAutoclass().getTerminalStorageClass())
+          .isEqualTo(StorageClass.ARCHIVE);
+
+      BucketInfo disabled =
+          remoteBucket
+              .toBuilder()
+              .setAutoclass(Autoclass.newBuilder().setEnabled(false).build())
+              .build();
+      Bucket updated = storage.update(disabled, BucketTargetOption.metagenerationMatch());
+
+      Autoclass updatedAutoclass = updated.getAutoclass();
+      assertThat(updatedAutoclass.getEnabled()).isFalse();
+      assertThat(updatedAutoclass.getTerminalStorageClass()).isNull();
+
+      assertThat(updatedAutoclass).isNotEqualTo(remoteBucketAutoclass);
+    }
+  }
+
   @Test
   public void testUpdateBucket_noModification() throws Exception {
     String bucketName = generator.randomBucketName();