This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 7b9d30eec7 Fix GCS Folder deletion for both HNS Enabled and HNS 
Disabled buckets (#7213)
7b9d30eec7 is described below

commit 7b9d30eec720bcddfe69047a9020d808219d621e
Author: Josh Downing <[email protected]>
AuthorDate: Tue Jun 2 09:49:47 2026 -0400

    Fix GCS Folder deletion for both HNS Enabled and HNS Disabled buckets 
(#7213)
    
    * folders should now get deleted in a hierarchical namespace enabled bucket
    
    * Fix delete folders on gcs for hns enabled and hns not enabled
    
    * Fix delete folders on gcs for hns enabled and hns not enabled
    
    * Fix delete folders on gcs for hns enabled and hns not enabled
---
 plugins/tech/google/pom.xml                        | 18 ++++++
 .../apache/hop/vfs/gs/GoogleStorageFileObject.java | 65 ++++++++++++++++------
 .../apache/hop/vfs/gs/GoogleStorageFileSystem.java | 20 +++++++
 3 files changed, 87 insertions(+), 16 deletions(-)

diff --git a/plugins/tech/google/pom.xml b/plugins/tech/google/pom.xml
index e48c754494..3236dbcdc7 100755
--- a/plugins/tech/google/pom.xml
+++ b/plugins/tech/google/pom.xml
@@ -157,6 +157,24 @@
                 </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>com.google.cloud</groupId>
+            <artifactId>google-cloud-storage-control</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.google.protobuf</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>io.grpc</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.conscrypt</groupId>
+                    <artifactId>conscrypt-openjdk-uber</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
         <dependency>
             <groupId>com.google.http-client</groupId>
             <artifactId>google-http-client</artifactId>
diff --git 
a/plugins/tech/google/src/main/java/org/apache/hop/vfs/gs/GoogleStorageFileObject.java
 
b/plugins/tech/google/src/main/java/org/apache/hop/vfs/gs/GoogleStorageFileObject.java
index 432807cc81..f487dd96c7 100644
--- 
a/plugins/tech/google/src/main/java/org/apache/hop/vfs/gs/GoogleStorageFileObject.java
+++ 
b/plugins/tech/google/src/main/java/org/apache/hop/vfs/gs/GoogleStorageFileObject.java
@@ -19,6 +19,7 @@
 package org.apache.hop.vfs.gs;
 
 import com.google.api.gax.paging.Page;
+import com.google.api.gax.rpc.NotFoundException;
 import com.google.cloud.storage.Blob;
 import com.google.cloud.storage.BlobId;
 import com.google.cloud.storage.BlobInfo;
@@ -26,6 +27,9 @@ import com.google.cloud.storage.Bucket;
 import com.google.cloud.storage.BucketInfo;
 import com.google.cloud.storage.Storage;
 import com.google.cloud.storage.Storage.BlobListOption;
+import com.google.storage.control.v2.DeleteFolderRequest;
+import com.google.storage.control.v2.FolderName;
+import com.google.storage.control.v2.StorageControlClient;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -200,6 +204,9 @@ public class GoogleStorageFileObject extends 
AbstractFileObject<GoogleStorageFil
       }
       Map<String, GoogleStorageListCache.ChildInfo> cacheEntries = new 
LinkedHashMap<>();
       for (Blob b : page.iterateAll()) {
+        if 
(stripTrailingSlash(b.getName()).equals(stripTrailingSlash(bucketPath))) {
+          continue;
+        }
         results.add(lastPathElement(stripTrailingSlash(b.getName())));
         FileType childType = b.isDirectory() ? FileType.FOLDER : FileType.FILE;
         long childSize = b.getSize() != null ? b.getSize() : 0L;
@@ -233,18 +240,55 @@ public class GoogleStorageFileObject extends 
AbstractFileObject<GoogleStorageFil
   protected void doDelete() throws Exception {
     if (hasObject()) {
       blob.delete();
+      if (isHnsEnabled()) {
+        handleHnsDirectoryDeletion(stripLeadingSlash(bucketPath));
+      }
     } else if (bucketName != null
         && !bucketName.isEmpty()
         && bucketPath != null
         && !bucketPath.isEmpty()) {
       Storage storage = getAbstractFileSystem().setupStorage();
-      storage.delete(BlobId.of(bucketName, stripLeadingSlash(bucketPath)));
+      String path = stripLeadingSlash(bucketPath);
+      Blob found = findBlob(storage, path);
+      if (found != null) {
+        found.delete();
+      }
+      if (isHnsEnabled()) {
+        handleHnsDirectoryDeletion(path);
+      }
     } else {
       throw new IOException("Cannot delete: missing bucket/object path for '" 
+ this + "'");
     }
     getAbstractFileSystem().invalidateListCacheForParentOf(bucketName, 
bucketPath);
   }
 
+  private Blob findBlob(Storage storage, String path) {
+    Blob found = storage.get(BlobId.of(bucketName, path));
+    if (found == null) {
+      found = storage.get(BlobId.of(bucketName, path + "/"));
+    }
+    return found;
+  }
+
+  private void handleHnsDirectoryDeletion(String path) throws IOException {
+    String folderName = FolderName.format("_", bucketName, 
stripTrailingSlash(path));
+    try {
+      StorageControlClient controlClient = 
getAbstractFileSystem().getStorageControlClient();
+      DeleteFolderRequest folderRequest =
+          DeleteFolderRequest.newBuilder().setName(folderName).build();
+      controlClient.deleteFolder(folderRequest);
+    } catch (NotFoundException e) {
+      // Not an HNS folder, skip.
+    }
+  }
+
+  private boolean isHnsEnabled() {
+    if (bucket == null || bucket.getHierarchicalNamespace() == null) {
+      return false;
+    }
+    return bucket.getHierarchicalNamespace().getEnabled();
+  }
+
   @Override
   protected void doDetach() throws Exception {
     bucket = null;
@@ -316,11 +360,7 @@ public class GoogleStorageFileObject extends 
AbstractFileObject<GoogleStorageFil
     if (!hasBucket()) {
       Page<Bucket> page = storage.list();
       for (Bucket b : page.iterateAll()) {
-        results.add(
-            new GoogleStorageFileObject(
-                scheme,
-                new GoogleStorageFileName(scheme, b.getName(), 
FileType.FOLDER),
-                getAbstractFileSystem()));
+        results.add(getAbstractFileSystem().resolveFile("/" + b.getName()));
       }
     } else {
       String prefix;
@@ -339,7 +379,7 @@ public class GoogleStorageFileObject extends 
AbstractFileObject<GoogleStorageFil
       }
       Map<String, GoogleStorageListCache.ChildInfo> cacheEntries = new 
LinkedHashMap<>();
       for (Blob b : page.iterateAll()) {
-        if (this.blob != null && b.getName().equals(this.blob.getName())) {
+        if 
(stripTrailingSlash(b.getName()).equals(stripTrailingSlash(bucketPath))) {
           continue;
         }
         FileType childType = b.isDirectory() ? FileType.FOLDER : FileType.FILE;
@@ -351,15 +391,8 @@ public class GoogleStorageFileObject extends 
AbstractFileObject<GoogleStorageFil
         cacheEntries.put(
             b.getName(), new GoogleStorageListCache.ChildInfo(childType, 
childSize, childLastMod));
         results.add(
-            new GoogleStorageFileObject(
-                scheme,
-                new GoogleStorageFileName(
-                    scheme,
-                    getName().getPath() + "/" + 
lastPathElement(stripTrailingSlash(b.getName())),
-                    childType),
-                getAbstractFileSystem(),
-                this.bucket != null ? bucket : storage.get(bucketName),
-                b));
+            getAbstractFileSystem()
+                .resolveFile("/" + bucketName + "/" + 
stripTrailingSlash(b.getName())));
       }
       if (!cacheEntries.isEmpty()) {
         getAbstractFileSystem().putListCache(bucketName, prefix, cacheEntries);
diff --git 
a/plugins/tech/google/src/main/java/org/apache/hop/vfs/gs/GoogleStorageFileSystem.java
 
b/plugins/tech/google/src/main/java/org/apache/hop/vfs/gs/GoogleStorageFileSystem.java
index 61b2d41b4e..72d2d7103a 100644
--- 
a/plugins/tech/google/src/main/java/org/apache/hop/vfs/gs/GoogleStorageFileSystem.java
+++ 
b/plugins/tech/google/src/main/java/org/apache/hop/vfs/gs/GoogleStorageFileSystem.java
@@ -18,10 +18,14 @@
 
 package org.apache.hop.vfs.gs;
 
+import com.google.api.gax.core.FixedCredentialsProvider;
 import com.google.api.gax.retrying.RetrySettings;
 import com.google.cloud.http.HttpTransportOptions;
 import com.google.cloud.storage.Storage;
 import com.google.cloud.storage.StorageOptions;
+import com.google.storage.control.v2.StorageControlClient;
+import com.google.storage.control.v2.StorageControlSettings;
+import java.io.IOException;
 import java.util.Collection;
 import org.apache.commons.vfs2.Capability;
 import org.apache.commons.vfs2.FileName;
@@ -38,6 +42,7 @@ import org.threeten.bp.Duration;
 public class GoogleStorageFileSystem extends AbstractFileSystem {
 
   Storage storage = null;
+  StorageControlClient storageControlClient = null;
   FileSystemOptions fileSystemOptions;
 
   private GoogleStorageListCache listCache;
@@ -150,4 +155,19 @@ public class GoogleStorageFileSystem extends 
AbstractFileSystem {
       return "";
     }
   }
+
+  StorageControlClient getStorageControlClient() throws IOException {
+    if (storageControlClient != null) {
+      return storageControlClient;
+    }
+    StorageControlSettings settings =
+        StorageControlSettings.newBuilder()
+            .setCredentialsProvider(
+                FixedCredentialsProvider.create(
+                    GoogleStorageFileSystemConfigBuilder.getInstance()
+                        .getGoogleCredentials(fileSystemOptions)))
+            .build();
+    storageControlClient = StorageControlClient.create(settings);
+    return storageControlClient;
+  }
 }

Reply via email to