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

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


The following commit(s) were added to refs/heads/main by this push:
     new 61b31676d [#5622] feat(client): support credential client in Gravition 
java client (#5753)
61b31676d is described below

commit 61b31676d004a50db829e4041df0bb3fd288b19f
Author: FANNG <[email protected]>
AuthorDate: Mon Dec 9 14:30:02 2024 +0800

    [#5622] feat(client): support credential client in Gravition java client 
(#5753)
    
    ### What changes were proposed in this pull request?
    
    support credential client in Gravition java client
    
    ### Why are the changes needed?
    
    Fix: #5622
    
    ### Does this PR introduce _any_ user-facing change?
    no
    
    ### How was this patch tested?
    
    add UT and test the overall flow in POC
---
 .../apache/gravitino/credential/Credential.java    |  10 ++
 .../gravitino/credential/GCSTokenCredential.java   |  36 ++++-
 .../gravitino/credential/OSSTokenCredential.java   |  40 ++++-
 .../credential/S3SecretKeyCredential.java          |  33 +++-
 .../gravitino/credential/S3TokenCredential.java    |  46 ++++--
 .../org.apache.gravitino.credential.Credential     |  23 +++
 .../apache/gravitino/client/BaseSchemaCatalog.java |   5 +
 .../org/apache/gravitino/client/ErrorHandlers.java |  47 ++++++
 .../apache/gravitino/client/FilesetCatalog.java    |  15 +-
 .../apache/gravitino/client/GenericFileset.java    |  17 +-
 .../client/MetadataObjectCredentialOperations.java |  62 ++++++++
 .../gravitino/client/TestSupportCredentials.java   | 175 +++++++++++++++++++++
 .../gravitino/credential/CredentialFactory.java    |  69 ++++++++
 .../gravitino/dto/credential/CredentialDTO.java    | 131 +++++++++++++++
 .../dto/responses/CredentialResponse.java          |  62 ++++++++
 .../apache/gravitino/dto/util/DTOConverters.java   |  52 ++++++
 .../gravitino/credential/TestCredentialDTO.java    |  50 ++++++
 .../credential/TestCredentialFactory.java          | 116 ++++++++++++++
 .../credential/TestCredentialPropertiesUtils.java  |   2 +-
 .../credential/DummyCredentialProvider.java        |   6 +
 .../service/extension/DummyCredentialProvider.java |   6 +
 21 files changed, 972 insertions(+), 31 deletions(-)

diff --git a/api/src/main/java/org/apache/gravitino/credential/Credential.java 
b/api/src/main/java/org/apache/gravitino/credential/Credential.java
index b2fdb1971..7ba9c4220 100644
--- a/api/src/main/java/org/apache/gravitino/credential/Credential.java
+++ b/api/src/main/java/org/apache/gravitino/credential/Credential.java
@@ -52,6 +52,16 @@ public interface Credential {
    */
   Map<String, String> credentialInfo();
 
+  /**
+   * Initialize the credential with the credential information.
+   *
+   * <p>This method is invoked to deserialize the credential in client side.
+   *
+   * @param credentialInfo The credential information from {@link 
#credentialInfo}.
+   * @param expireTimeInMs The expire-time from {@link #expireTimeInMs()}.
+   */
+  void initialize(Map<String, String> credentialInfo, long expireTimeInMs);
+
   /**
    * Converts the credential to properties to transfer the credential though 
API.
    *
diff --git 
a/api/src/main/java/org/apache/gravitino/credential/GCSTokenCredential.java 
b/api/src/main/java/org/apache/gravitino/credential/GCSTokenCredential.java
index 59dc3b246..d55799c52 100644
--- a/api/src/main/java/org/apache/gravitino/credential/GCSTokenCredential.java
+++ b/api/src/main/java/org/apache/gravitino/credential/GCSTokenCredential.java
@@ -33,20 +33,25 @@ public class GCSTokenCredential implements Credential {
   /** GCS credential property, token name. */
   public static final String GCS_TOKEN_NAME = "token";
 
-  private final String token;
-  private final long expireMs;
+  private String token;
+  private long expireTimeInMs;
 
   /**
    * @param token The GCS token.
-   * @param expireMs The GCS token expire time at ms.
+   * @param expireTimeInMs The GCS token expire time at ms.
    */
-  public GCSTokenCredential(String token, long expireMs) {
-    Preconditions.checkArgument(
-        StringUtils.isNotBlank(token), "GCS session token should not be null");
+  public GCSTokenCredential(String token, long expireTimeInMs) {
+    validate(token, expireTimeInMs);
     this.token = token;
-    this.expireMs = expireMs;
+    this.expireTimeInMs = expireTimeInMs;
   }
 
+  /**
+   * This is the constructor that is used by credential factory to create an 
instance of credential
+   * according to the credential information.
+   */
+  public GCSTokenCredential() {}
+
   @Override
   public String credentialType() {
     return GCS_TOKEN_CREDENTIAL_TYPE;
@@ -54,7 +59,7 @@ public class GCSTokenCredential implements Credential {
 
   @Override
   public long expireTimeInMs() {
-    return expireMs;
+    return expireTimeInMs;
   }
 
   @Override
@@ -62,6 +67,14 @@ public class GCSTokenCredential implements Credential {
     return (new ImmutableMap.Builder<String, String>()).put(GCS_TOKEN_NAME, 
token).build();
   }
 
+  @Override
+  public void initialize(Map<String, String> credentialInfo, long 
expireTimeInMs) {
+    String token = credentialInfo.get(GCS_TOKEN_NAME);
+    validate(token, expireTimeInMs);
+    this.token = token;
+    this.expireTimeInMs = expireTimeInMs;
+  }
+
   /**
    * Get GCS token.
    *
@@ -70,4 +83,11 @@ public class GCSTokenCredential implements Credential {
   public String token() {
     return token;
   }
+
+  private void validate(String token, long expireTimeInMs) {
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(token), "GCS session token should not be 
empty");
+    Preconditions.checkArgument(
+        expireTimeInMs > 0, "The expire time of GcsTokenCredential should 
great than 0");
+  }
 }
diff --git 
a/api/src/main/java/org/apache/gravitino/credential/OSSTokenCredential.java 
b/api/src/main/java/org/apache/gravitino/credential/OSSTokenCredential.java
index 308d76a8d..edf23f207 100644
--- a/api/src/main/java/org/apache/gravitino/credential/OSSTokenCredential.java
+++ b/api/src/main/java/org/apache/gravitino/credential/OSSTokenCredential.java
@@ -36,10 +36,10 @@ public class OSSTokenCredential implements Credential {
   /** OSS security token. */
   public static final String GRAVITINO_OSS_TOKEN = "oss-security-token";
 
-  private final String accessKeyId;
-  private final String secretAccessKey;
-  private final String securityToken;
-  private final long expireTimeInMS;
+  private String accessKeyId;
+  private String secretAccessKey;
+  private String securityToken;
+  private long expireTimeInMS;
 
   /**
    * Constructs an instance of {@link OSSTokenCredential} with secret key and 
token.
@@ -64,6 +64,12 @@ public class OSSTokenCredential implements Credential {
     this.expireTimeInMS = expireTimeInMS;
   }
 
+  /**
+   * This is the constructor that is used by credential factory to create an 
instance of credential
+   * according to the credential information.
+   */
+  public OSSTokenCredential() {}
+
   @Override
   public String credentialType() {
     return OSS_TOKEN_CREDENTIAL_TYPE;
@@ -83,6 +89,20 @@ public class OSSTokenCredential implements Credential {
         .build();
   }
 
+  @Override
+  public void initialize(Map<String, String> credentialInfo, long 
expireTimeInMs) {
+    String accessKeyId = 
credentialInfo.get(GRAVITINO_OSS_SESSION_ACCESS_KEY_ID);
+    String secretAccessKey = 
credentialInfo.get(GRAVITINO_OSS_SESSION_SECRET_ACCESS_KEY);
+    String securityToken = credentialInfo.get(GRAVITINO_OSS_TOKEN);
+
+    validate(accessKeyId, secretAccessKey, securityToken, expireTimeInMs);
+
+    this.accessKeyId = accessKeyId;
+    this.secretAccessKey = secretAccessKey;
+    this.securityToken = securityToken;
+    this.expireTimeInMS = expireTimeInMs;
+  }
+
   /**
    * Get oss access key ID.
    *
@@ -109,4 +129,16 @@ public class OSSTokenCredential implements Credential {
   public String securityToken() {
     return securityToken;
   }
+
+  private void validate(
+      String accessKeyId, String secretAccessKey, String sessionToken, long 
expireTimeInMs) {
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(accessKeyId), "S3 access key Id should not be 
empty");
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(secretAccessKey), "S3 secret access key should 
not be empty");
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(sessionToken), "S3 session token should not be 
empty");
+    Preconditions.checkArgument(
+        expireTimeInMs > 0, "The expire time of S3TokenCredential should great 
than 0");
+  }
 }
diff --git 
a/api/src/main/java/org/apache/gravitino/credential/S3SecretKeyCredential.java 
b/api/src/main/java/org/apache/gravitino/credential/S3SecretKeyCredential.java
index 3063d5615..d9b2b6092 100644
--- 
a/api/src/main/java/org/apache/gravitino/credential/S3SecretKeyCredential.java
+++ 
b/api/src/main/java/org/apache/gravitino/credential/S3SecretKeyCredential.java
@@ -22,6 +22,7 @@ package org.apache.gravitino.credential;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
 
 /** S3 secret key credential. */
 public class S3SecretKeyCredential implements Credential {
@@ -33,8 +34,8 @@ public class S3SecretKeyCredential implements Credential {
   /** The static secret access key used to access S3 data. */
   public static final String GRAVITINO_S3_STATIC_SECRET_ACCESS_KEY = 
"s3-secret-access-key";
 
-  private final String accessKeyId;
-  private final String secretAccessKey;
+  private String accessKeyId;
+  private String secretAccessKey;
 
   /**
    * Constructs an instance of {@link S3SecretKeyCredential} with the static 
S3 access key ID and
@@ -44,13 +45,17 @@ public class S3SecretKeyCredential implements Credential {
    * @param secretAccessKey The S3 static secret access key.
    */
   public S3SecretKeyCredential(String accessKeyId, String secretAccessKey) {
-    Preconditions.checkNotNull(accessKeyId, "S3 access key Id should not 
null");
-    Preconditions.checkNotNull(secretAccessKey, "S3 secret access key should 
not null");
-
+    validate(accessKeyId, secretAccessKey, 0);
     this.accessKeyId = accessKeyId;
     this.secretAccessKey = secretAccessKey;
   }
 
+  /**
+   * This is the constructor that is used by credential factory to create an 
instance of credential
+   * according to the credential information.
+   */
+  public S3SecretKeyCredential() {}
+
   @Override
   public String credentialType() {
     return S3_SECRET_KEY_CREDENTIAL_TYPE;
@@ -69,6 +74,15 @@ public class S3SecretKeyCredential implements Credential {
         .build();
   }
 
+  @Override
+  public void initialize(Map<String, String> credentialInfo, long 
expireTimeInMs) {
+    String accessKeyId = credentialInfo.get(GRAVITINO_S3_STATIC_ACCESS_KEY_ID);
+    String secretAccessKey = 
credentialInfo.get(GRAVITINO_S3_STATIC_SECRET_ACCESS_KEY);
+    validate(accessKeyId, secretAccessKey, expireTimeInMs);
+    this.accessKeyId = accessKeyId;
+    this.secretAccessKey = secretAccessKey;
+  }
+
   /**
    * Get S3 static access key ID.
    *
@@ -86,4 +100,13 @@ public class S3SecretKeyCredential implements Credential {
   public String secretAccessKey() {
     return secretAccessKey;
   }
+
+  private void validate(String accessKeyId, String secretAccessKey, long 
expireTimeInMs) {
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(accessKeyId), "S3 access key Id should not 
empty");
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(secretAccessKey), "S3 secret access key should 
not empty");
+    Preconditions.checkArgument(
+        expireTimeInMs == 0, "The expire time of S3SecretKeyCredential is not 
0");
+  }
 }
diff --git 
a/api/src/main/java/org/apache/gravitino/credential/S3TokenCredential.java 
b/api/src/main/java/org/apache/gravitino/credential/S3TokenCredential.java
index d30a7e140..89d7de760 100644
--- a/api/src/main/java/org/apache/gravitino/credential/S3TokenCredential.java
+++ b/api/src/main/java/org/apache/gravitino/credential/S3TokenCredential.java
@@ -36,10 +36,10 @@ public class S3TokenCredential implements Credential {
   /** S3 session token. */
   public static final String GRAVITINO_S3_TOKEN = "s3-session-token";
 
-  private final String accessKeyId;
-  private final String secretAccessKey;
-  private final String sessionToken;
-  private final long expireTimeInMS;
+  private String accessKeyId;
+  private String secretAccessKey;
+  private String sessionToken;
+  private long expireTimeInMS;
 
   /**
    * Constructs an instance of {@link S3SecretKeyCredential} with session 
secret key and token.
@@ -51,19 +51,19 @@ public class S3TokenCredential implements Credential {
    */
   public S3TokenCredential(
       String accessKeyId, String secretAccessKey, String sessionToken, long 
expireTimeInMS) {
-    Preconditions.checkArgument(
-        StringUtils.isNotBlank(accessKeyId), "S3 access key Id should not be 
empty");
-    Preconditions.checkArgument(
-        StringUtils.isNotBlank(secretAccessKey), "S3 secret access key should 
not be empty");
-    Preconditions.checkArgument(
-        StringUtils.isNotBlank(sessionToken), "S3 session token should not be 
empty");
-
+    validate(accessKeyId, secretAccessKey, sessionToken, expireTimeInMS);
     this.accessKeyId = accessKeyId;
     this.secretAccessKey = secretAccessKey;
     this.sessionToken = sessionToken;
     this.expireTimeInMS = expireTimeInMS;
   }
 
+  /**
+   * This is the constructor that is used by credential factory to create an 
instance of credential
+   * according to the credential information.
+   */
+  public S3TokenCredential() {}
+
   @Override
   public String credentialType() {
     return S3_TOKEN_CREDENTIAL_TYPE;
@@ -83,6 +83,18 @@ public class S3TokenCredential implements Credential {
         .build();
   }
 
+  @Override
+  public void initialize(Map<String, String> credentialInfo, long 
expireTimeInMs) {
+    String accessKeyId = 
credentialInfo.get(GRAVITINO_S3_SESSION_ACCESS_KEY_ID);
+    String secretAccessKey = 
credentialInfo.get(GRAVITINO_S3_SESSION_SECRET_ACCESS_KEY);
+    String sessionToken = credentialInfo.get(GRAVITINO_S3_TOKEN);
+    validate(accessKeyId, secretAccessKey, sessionToken, expireTimeInMs);
+    this.accessKeyId = accessKeyId;
+    this.secretAccessKey = secretAccessKey;
+    this.sessionToken = sessionToken;
+    this.expireTimeInMS = expireTimeInMs;
+  }
+
   /**
    * Get S3 session access key ID.
    *
@@ -109,4 +121,16 @@ public class S3TokenCredential implements Credential {
   public String sessionToken() {
     return sessionToken;
   }
+
+  private void validate(
+      String accessKeyId, String secretAccessKey, String sessionToken, long 
expireTimeInMs) {
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(accessKeyId), "S3 access key Id should not be 
empty");
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(secretAccessKey), "S3 secret access key should 
not be empty");
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(sessionToken), "S3 session token should not be 
empty");
+    Preconditions.checkArgument(
+        expireTimeInMs > 0, "The expire time of S3TokenCredential should great 
than 0");
+  }
 }
diff --git 
a/api/src/main/resources/META-INF/services/org.apache.gravitino.credential.Credential
 
b/api/src/main/resources/META-INF/services/org.apache.gravitino.credential.Credential
new file mode 100644
index 000000000..91d061be7
--- /dev/null
+++ 
b/api/src/main/resources/META-INF/services/org.apache.gravitino.credential.Credential
@@ -0,0 +1,23 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+org.apache.gravitino.credential.S3TokenCredential
+org.apache.gravitino.credential.S3SecretKeyCredential
+org.apache.gravitino.credential.GCSTokenCredential
+org.apache.gravitino.credential.OSSTokenCredential
diff --git 
a/clients/client-java/src/main/java/org/apache/gravitino/client/BaseSchemaCatalog.java
 
b/clients/client-java/src/main/java/org/apache/gravitino/client/BaseSchemaCatalog.java
index 9359ea439..d0a4d9db1 100644
--- 
a/clients/client-java/src/main/java/org/apache/gravitino/client/BaseSchemaCatalog.java
+++ 
b/clients/client-java/src/main/java/org/apache/gravitino/client/BaseSchemaCatalog.java
@@ -55,6 +55,7 @@ import org.apache.gravitino.tag.Tag;
  */
 abstract class BaseSchemaCatalog extends CatalogDTO
     implements Catalog, SupportsSchemas, SupportsTags, SupportsRoles {
+
   /** The REST client to send the requests. */
   protected final RESTClient restClient;
 
@@ -63,6 +64,7 @@ abstract class BaseSchemaCatalog extends CatalogDTO
 
   private final MetadataObjectTagOperations objectTagOperations;
   private final MetadataObjectRoleOperations objectRoleOperations;
+  protected final MetadataObjectCredentialOperations 
objectCredentialOperations;
 
   BaseSchemaCatalog(
       Namespace catalogNamespace,
@@ -88,6 +90,9 @@ abstract class BaseSchemaCatalog extends CatalogDTO
         new MetadataObjectTagOperations(catalogNamespace.level(0), 
metadataObject, restClient);
     this.objectRoleOperations =
         new MetadataObjectRoleOperations(catalogNamespace.level(0), 
metadataObject, restClient);
+    this.objectCredentialOperations =
+        new MetadataObjectCredentialOperations(
+            catalogNamespace.level(0), metadataObject, restClient);
   }
 
   @Override
diff --git 
a/clients/client-java/src/main/java/org/apache/gravitino/client/ErrorHandlers.java
 
b/clients/client-java/src/main/java/org/apache/gravitino/client/ErrorHandlers.java
index 3dcf6672a..776300a5a 100644
--- 
a/clients/client-java/src/main/java/org/apache/gravitino/client/ErrorHandlers.java
+++ 
b/clients/client-java/src/main/java/org/apache/gravitino/client/ErrorHandlers.java
@@ -42,6 +42,7 @@ import 
org.apache.gravitino.exceptions.MetalakeAlreadyExistsException;
 import org.apache.gravitino.exceptions.MetalakeInUseException;
 import org.apache.gravitino.exceptions.MetalakeNotInUseException;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchCredentialException;
 import org.apache.gravitino.exceptions.NoSuchFilesetException;
 import org.apache.gravitino.exceptions.NoSuchGroupException;
 import org.apache.gravitino.exceptions.NoSuchMetadataObjectException;
@@ -201,6 +202,15 @@ public class ErrorHandlers {
     return TagErrorHandler.INSTANCE;
   }
 
+  /**
+   * Creates an error handler specific to credential operations.
+   *
+   * @return A Consumer representing the credential error handler.
+   */
+  public static Consumer<ErrorResponse> credentialErrorHandler() {
+    return CredentialErrorHandler.INSTANCE;
+  }
+
   /**
    * Creates an error handler specific to Owner operations.
    *
@@ -858,6 +868,43 @@ public class ErrorHandlers {
     }
   }
 
+  /** Error handler specific to Credential operations. */
+  @SuppressWarnings("FormatStringAnnotation")
+  private static class CredentialErrorHandler extends RestErrorHandler {
+
+    private static final CredentialErrorHandler INSTANCE = new 
CredentialErrorHandler();
+
+    @Override
+    public void accept(ErrorResponse errorResponse) {
+      String errorMessage = formatErrorMessage(errorResponse);
+
+      switch (errorResponse.getCode()) {
+        case ErrorConstants.ILLEGAL_ARGUMENTS_CODE:
+          throw new IllegalArgumentException(errorMessage);
+
+        case ErrorConstants.NOT_FOUND_CODE:
+          if 
(errorResponse.getType().equals(NoSuchMetalakeException.class.getSimpleName())) 
{
+            throw new NoSuchMetalakeException(errorMessage);
+          } else if (errorResponse
+              .getType()
+              .equals(NoSuchCredentialException.class.getSimpleName())) {
+            throw new NoSuchCredentialException(errorMessage);
+          } else {
+            throw new NotFoundException(errorMessage);
+          }
+
+        case ErrorConstants.NOT_IN_USE_CODE:
+          throw new MetalakeNotInUseException(errorMessage);
+
+        case ErrorConstants.INTERNAL_ERROR_CODE:
+          throw new RuntimeException(errorMessage);
+
+        default:
+          super.accept(errorResponse);
+      }
+    }
+  }
+
   /** Error handler specific to Tag operations. */
   @SuppressWarnings("FormatStringAnnotation")
   private static class TagErrorHandler extends RestErrorHandler {
diff --git 
a/clients/client-java/src/main/java/org/apache/gravitino/client/FilesetCatalog.java
 
b/clients/client-java/src/main/java/org/apache/gravitino/client/FilesetCatalog.java
index a57482ff5..a58075aaf 100644
--- 
a/clients/client-java/src/main/java/org/apache/gravitino/client/FilesetCatalog.java
+++ 
b/clients/client-java/src/main/java/org/apache/gravitino/client/FilesetCatalog.java
@@ -31,6 +31,8 @@ import org.apache.gravitino.Catalog;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.audit.CallerContext;
+import org.apache.gravitino.credential.Credential;
+import org.apache.gravitino.credential.SupportsCredentials;
 import org.apache.gravitino.dto.AuditDTO;
 import org.apache.gravitino.dto.CatalogDTO;
 import org.apache.gravitino.dto.requests.FilesetCreateRequest;
@@ -52,7 +54,8 @@ import org.apache.gravitino.rest.RESTUtils;
  * example, schemas and filesets list, creation, update and deletion. A 
Fileset catalog is under the
  * metalake.
  */
-class FilesetCatalog extends BaseSchemaCatalog implements 
org.apache.gravitino.file.FilesetCatalog {
+class FilesetCatalog extends BaseSchemaCatalog
+    implements org.apache.gravitino.file.FilesetCatalog, SupportsCredentials {
 
   FilesetCatalog(
       Namespace namespace,
@@ -265,6 +268,16 @@ class FilesetCatalog extends BaseSchemaCatalog implements 
org.apache.gravitino.f
     }
   }
 
+  @Override
+  public SupportsCredentials supportsCredentials() throws 
UnsupportedOperationException {
+    return this;
+  }
+
+  @Override
+  public Credential[] getCredentials() {
+    return objectCredentialOperations.getCredentials();
+  }
+
   @VisibleForTesting
   static String formatFilesetRequestPath(Namespace ns) {
     Namespace schemaNs = Namespace.of(ns.level(0), ns.level(1));
diff --git 
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericFileset.java
 
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericFileset.java
index 68eda6985..6e587b115 100644
--- 
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericFileset.java
+++ 
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericFileset.java
@@ -27,6 +27,8 @@ import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.MetadataObjects;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.authorization.SupportsRoles;
+import org.apache.gravitino.credential.Credential;
+import org.apache.gravitino.credential.SupportsCredentials;
 import org.apache.gravitino.dto.file.FilesetDTO;
 import org.apache.gravitino.exceptions.NoSuchTagException;
 import org.apache.gravitino.file.Fileset;
@@ -34,12 +36,13 @@ import org.apache.gravitino.tag.SupportsTags;
 import org.apache.gravitino.tag.Tag;
 
 /** Represents a generic fileset. */
-class GenericFileset implements Fileset, SupportsTags, SupportsRoles {
+class GenericFileset implements Fileset, SupportsTags, SupportsRoles, 
SupportsCredentials {
 
   private final FilesetDTO filesetDTO;
 
   private final MetadataObjectTagOperations objectTagOperations;
   private final MetadataObjectRoleOperations objectRoleOperations;
+  private final MetadataObjectCredentialOperations objectCredentialOperations;
 
   GenericFileset(FilesetDTO filesetDTO, RESTClient restClient, Namespace 
filesetNs) {
     this.filesetDTO = filesetDTO;
@@ -50,6 +53,8 @@ class GenericFileset implements Fileset, SupportsTags, 
SupportsRoles {
         new MetadataObjectTagOperations(filesetNs.level(0), filesetObject, 
restClient);
     this.objectRoleOperations =
         new MetadataObjectRoleOperations(filesetNs.level(0), filesetObject, 
restClient);
+    this.objectCredentialOperations =
+        new MetadataObjectCredentialOperations(filesetNs.level(0), 
filesetObject, restClient);
   }
 
   @Override
@@ -93,6 +98,11 @@ class GenericFileset implements Fileset, SupportsTags, 
SupportsRoles {
     return this;
   }
 
+  @Override
+  public SupportsCredentials supportsCredentials() {
+    return this;
+  }
+
   @Override
   public String[] listTags() {
     return objectTagOperations.listTags();
@@ -118,6 +128,11 @@ class GenericFileset implements Fileset, SupportsTags, 
SupportsRoles {
     return objectRoleOperations.listBindingRoleNames();
   }
 
+  @Override
+  public Credential[] getCredentials() {
+    return objectCredentialOperations.getCredentials();
+  }
+
   @Override
   public boolean equals(Object obj) {
     if (this == obj) {
diff --git 
a/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectCredentialOperations.java
 
b/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectCredentialOperations.java
new file mode 100644
index 000000000..b11fd9caf
--- /dev/null
+++ 
b/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectCredentialOperations.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.client;
+
+import java.util.Collections;
+import java.util.Locale;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.credential.Credential;
+import org.apache.gravitino.credential.SupportsCredentials;
+import org.apache.gravitino.dto.responses.CredentialResponse;
+import org.apache.gravitino.dto.util.DTOConverters;
+
+/**
+ * The implementation of {@link SupportsCredentials}. This interface will be 
composited into
+ * catalog, table, fileset to provide credential operations for these metadata 
objects
+ */
+class MetadataObjectCredentialOperations implements SupportsCredentials {
+
+  private final RESTClient restClient;
+
+  private final String credentialRequestPath;
+
+  MetadataObjectCredentialOperations(
+      String metalakeName, MetadataObject metadataObject, RESTClient 
restClient) {
+    this.restClient = restClient;
+    this.credentialRequestPath =
+        String.format(
+            "api/metalakes/%s/objects/%s/%s/credentials",
+            metalakeName,
+            metadataObject.type().name().toLowerCase(Locale.ROOT),
+            metadataObject.fullName());
+  }
+
+  @Override
+  public Credential[] getCredentials() {
+    CredentialResponse resp =
+        restClient.get(
+            credentialRequestPath,
+            CredentialResponse.class,
+            Collections.emptyMap(),
+            ErrorHandlers.credentialErrorHandler());
+
+    resp.validate();
+    return DTOConverters.fromDTO(resp.getCredentials());
+  }
+}
diff --git 
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportCredentials.java
 
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportCredentials.java
new file mode 100644
index 000000000..7b0817c8b
--- /dev/null
+++ 
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportCredentials.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.client;
+
+import static org.apache.hc.core5.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
+import static org.apache.hc.core5.http.HttpStatus.SC_NOT_FOUND;
+import static org.apache.hc.core5.http.HttpStatus.SC_OK;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import java.util.Collections;
+import java.util.Locale;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.MetadataObjects;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.credential.Credential;
+import org.apache.gravitino.credential.GCSTokenCredential;
+import org.apache.gravitino.credential.S3SecretKeyCredential;
+import org.apache.gravitino.credential.SupportsCredentials;
+import org.apache.gravitino.dto.AuditDTO;
+import org.apache.gravitino.dto.credential.CredentialDTO;
+import org.apache.gravitino.dto.file.FilesetDTO;
+import org.apache.gravitino.dto.responses.CredentialResponse;
+import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.dto.util.DTOConverters;
+import org.apache.gravitino.exceptions.NoSuchCredentialException;
+import org.apache.gravitino.file.Fileset;
+import org.apache.hc.core5.http.Method;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class TestSupportCredentials extends TestBase {
+
+  private static final String METALAKE_NAME = "metalake";
+
+  private static Catalog filesetCatalog;
+
+  private static Fileset genericFileset;
+
+  @BeforeAll
+  public static void setUp() throws Exception {
+    TestBase.setUp();
+    TestGravitinoMetalake.createMetalake(client, METALAKE_NAME);
+
+    filesetCatalog =
+        new FilesetCatalog(
+            Namespace.of(METALAKE_NAME),
+            "catalog2",
+            Catalog.Type.FILESET,
+            "test",
+            "comment",
+            Collections.emptyMap(),
+            AuditDTO.builder().build(),
+            client.restClient());
+
+    genericFileset =
+        new GenericFileset(
+            FilesetDTO.builder()
+                .name("fileset1")
+                .comment("comment1")
+                .type(Fileset.Type.EXTERNAL)
+                .storageLocation("s3://bucket/path")
+                .properties(Collections.emptyMap())
+                .audit(AuditDTO.builder().withCreator("test").build())
+                .build(),
+            client.restClient(),
+            Namespace.of(METALAKE_NAME, "catalog1", "schema1"));
+  }
+
+  @Test
+  public void testGetCredentialsForCatalog() throws JsonProcessingException {
+    testGetCredentials(
+        filesetCatalog.supportsCredentials(),
+        MetadataObjects.of(null, filesetCatalog.name(), 
MetadataObject.Type.CATALOG));
+  }
+
+  @Test
+  public void testGetCredentialsForFileset() throws JsonProcessingException {
+    testGetCredentials(
+        genericFileset.supportsCredentials(),
+        MetadataObjects.of("catalog1.schema1", genericFileset.name(), 
MetadataObject.Type.FILESET));
+  }
+
+  private void testGetCredentials(
+      SupportsCredentials supportsCredentials, MetadataObject metadataObject)
+      throws JsonProcessingException {
+    String path =
+        "/api/metalakes/"
+            + METALAKE_NAME
+            + "/objects/"
+            + metadataObject.type().name().toLowerCase(Locale.ROOT)
+            + "/"
+            + metadataObject.fullName()
+            + "/credentials";
+
+    S3SecretKeyCredential secretKeyCredential =
+        new S3SecretKeyCredential("access-id", "secret-key");
+    GCSTokenCredential gcsTokenCredential = new GCSTokenCredential("token", 
100);
+
+    // Return one credential
+    CredentialResponse resp =
+        new CredentialResponse(DTOConverters.toDTO(new Credential[] 
{secretKeyCredential}));
+    buildMockResource(Method.GET, path, null, resp, SC_OK);
+
+    Credential[] credentials = supportsCredentials.getCredentials();
+    Assertions.assertEquals(1, credentials.length);
+
+    Assertions.assertTrue(credentials[0] instanceof S3SecretKeyCredential);
+    S3SecretKeyCredential s3CredentialInClient = (S3SecretKeyCredential) 
credentials[0];
+    Assertions.assertEquals("access-id", s3CredentialInClient.accessKeyId());
+    Assertions.assertEquals("secret-key", 
s3CredentialInClient.secretAccessKey());
+    Assertions.assertEquals(0, s3CredentialInClient.expireTimeInMs());
+
+    // Return multi credentials
+    resp =
+        new CredentialResponse(
+            DTOConverters.toDTO(new Credential[] {secretKeyCredential, 
gcsTokenCredential}));
+    buildMockResource(Method.GET, path, null, resp, SC_OK);
+
+    credentials = supportsCredentials.getCredentials();
+    Assertions.assertEquals(2, credentials.length);
+
+    Assertions.assertTrue(credentials[0] instanceof S3SecretKeyCredential);
+    s3CredentialInClient = (S3SecretKeyCredential) credentials[0];
+    Assertions.assertEquals("access-id", s3CredentialInClient.accessKeyId());
+    Assertions.assertEquals("secret-key", 
s3CredentialInClient.secretAccessKey());
+    Assertions.assertEquals(0, s3CredentialInClient.expireTimeInMs());
+
+    Assertions.assertTrue(credentials[1] instanceof GCSTokenCredential);
+    GCSTokenCredential gcsCredentialInClient = (GCSTokenCredential) 
credentials[1];
+    Assertions.assertEquals("token", gcsCredentialInClient.token());
+    Assertions.assertEquals(100, gcsCredentialInClient.expireTimeInMs());
+
+    // Return empty list
+    resp = new CredentialResponse(new CredentialDTO[0]);
+    buildMockResource(Method.GET, path, null, resp, SC_OK);
+    credentials = supportsCredentials.getCredentials();
+    Assertions.assertEquals(0, credentials.length);
+
+    // Test throw NoSuchCredentialException
+    ErrorResponse errorResp =
+        
ErrorResponse.notFound(NoSuchCredentialException.class.getSimpleName(), "mock 
error");
+    buildMockResource(Method.GET, path, null, errorResp, SC_NOT_FOUND);
+
+    Throwable ex =
+        Assertions.assertThrows(
+            NoSuchCredentialException.class, () -> 
supportsCredentials.getCredentials());
+    Assertions.assertTrue(ex.getMessage().contains("mock error"));
+
+    // Test throw internal error
+    ErrorResponse errorResp1 = ErrorResponse.internalError("mock error");
+    buildMockResource(Method.GET, path, null, errorResp1, 
SC_INTERNAL_SERVER_ERROR);
+
+    Throwable ex1 =
+        Assertions.assertThrows(RuntimeException.class, () -> 
supportsCredentials.getCredentials());
+    Assertions.assertTrue(ex1.getMessage().contains("mock error"));
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/credential/CredentialFactory.java 
b/common/src/main/java/org/apache/gravitino/credential/CredentialFactory.java
new file mode 100644
index 000000000..0ba46301b
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/credential/CredentialFactory.java
@@ -0,0 +1,69 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.gravitino.credential;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Streams;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.stream.Collectors;
+
+/** Create a specific credential according to the credential information. */
+public class CredentialFactory {
+  /**
+   * Creates a {@link Credential} instance based on the provided credential 
type, information, and
+   * expiration time.
+   *
+   * @param credentialType The type of the credential to be created. This 
string is used to look up
+   *     the corresponding credential class.
+   * @param credentialInfo A {@link Map} containing key-value pairs of 
information needed to
+   *     initialize the credential.
+   * @param expireTimeInMs The expiration time of the credential in 
milliseconds.
+   * @return A newly created and initialized {@link Credential} object.
+   */
+  public static Credential create(
+      String credentialType, Map<String, String> credentialInfo, long 
expireTimeInMs) {
+    Class<? extends Credential> credentialClz = 
lookupCredential(credentialType);
+    try {
+      Credential credential = 
credentialClz.getDeclaredConstructor().newInstance();
+      credential.initialize(credentialInfo, expireTimeInMs);
+      return credential;
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private static Class<? extends Credential> lookupCredential(String 
credentialType) {
+    ServiceLoader<Credential> serviceLoader = 
ServiceLoader.load(Credential.class);
+    List<Class<? extends Credential>> credentials =
+        Streams.stream(serviceLoader.iterator())
+            .filter(credential -> 
credentialType.equalsIgnoreCase(credential.credentialType()))
+            .map(Credential::getClass)
+            .collect(Collectors.toList());
+    if (credentials.isEmpty()) {
+      throw new RuntimeException("No credential found for: " + credentialType);
+    } else if (credentials.size() > 1) {
+      throw new RuntimeException("Multiple credential found for: " + 
credentialType);
+    } else {
+      return Iterables.getOnlyElement(credentials);
+    }
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/credential/CredentialDTO.java 
b/common/src/main/java/org/apache/gravitino/dto/credential/CredentialDTO.java
new file mode 100644
index 000000000..506ab5f1f
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/credential/CredentialDTO.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.dto.credential;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Objects;
+import java.util.Map;
+import org.apache.gravitino.credential.Credential;
+
+/** Represents a credential Data Transfer Object (DTO). */
+public class CredentialDTO implements Credential {
+
+  @JsonProperty("credentialType")
+  private String credentialType;
+
+  @JsonProperty("expireTimeInMs")
+  private long expireTimeInMs;
+
+  @JsonProperty("credentialInfo")
+  private Map<String, String> credentialInfo;
+
+  private CredentialDTO() {}
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof CredentialDTO)) {
+      return false;
+    }
+
+    CredentialDTO credentialDTO = (CredentialDTO) o;
+    return Objects.equal(credentialType, credentialDTO.credentialType)
+        && Objects.equal(expireTimeInMs, credentialDTO.expireTimeInMs)
+        && Objects.equal(credentialInfo, credentialDTO.credentialInfo);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(credentialType, expireTimeInMs, credentialInfo);
+  }
+
+  @Override
+  public String credentialType() {
+    return credentialType;
+  }
+
+  @Override
+  public long expireTimeInMs() {
+    return expireTimeInMs;
+  }
+
+  @Override
+  public Map<String, String> credentialInfo() {
+    return credentialInfo;
+  }
+
+  @Override
+  public void initialize(Map<String, String> credentialInfo, long 
expireTimeInMs) {
+    throw new UnsupportedOperationException("CredentialDTO doesn't support 
initWithCredentialInfo");
+  }
+
+  /** @return a new builder for constructing a Credential DTO. */
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /** Builder class for constructing CredentialDTO instances. */
+  public static class Builder {
+    private final CredentialDTO credentialDTO;
+
+    private Builder() {
+      credentialDTO = new CredentialDTO();
+    }
+
+    /**
+     * Sets the credential type.
+     *
+     * @param credentialType The specific credential type.
+     * @return The builder instance.
+     */
+    public Builder withCredentialType(String credentialType) {
+      credentialDTO.credentialType = credentialType;
+      return this;
+    }
+
+    /**
+     * Sets the credential expire time.
+     *
+     * @param expireTimeInMs The credential expire time.
+     * @return The builder instance.
+     */
+    public Builder withExpireTimeInMs(long expireTimeInMs) {
+      credentialDTO.expireTimeInMs = expireTimeInMs;
+      return this;
+    }
+
+    /**
+     * Sets the credential information.
+     *
+     * @param credentialInfo The specific credential information map.
+     * @return The builder instance.
+     */
+    public Builder withCredentialInfo(Map<String, String> credentialInfo) {
+      credentialDTO.credentialInfo = credentialInfo;
+      return this;
+    }
+
+    /** @return The constructed credential DTO. */
+    public CredentialDTO build() {
+      return credentialDTO;
+    }
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/responses/CredentialResponse.java
 
b/common/src/main/java/org/apache/gravitino/dto/responses/CredentialResponse.java
new file mode 100644
index 000000000..88d904b7f
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/responses/CredentialResponse.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.dto.responses;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.gravitino.dto.credential.CredentialDTO;
+
+/** Represents a response for credentials. */
+@Getter
+@ToString
+@EqualsAndHashCode(callSuper = true)
+public class CredentialResponse extends BaseResponse {
+
+  @JsonProperty("credentials")
+  private final CredentialDTO[] credentials;
+
+  /**
+   * Creates a new CredentialResponse.
+   *
+   * @param credentials The credentials.
+   */
+  public CredentialResponse(CredentialDTO[] credentials) {
+    super(0);
+    this.credentials = credentials;
+  }
+
+  /**
+   * This is the constructor that is used by Jackson deserializer to create an 
instance of
+   * CredentialResponse.
+   */
+  public CredentialResponse() {
+    super();
+    this.credentials = null;
+  }
+
+  @Override
+  public void validate() throws IllegalArgumentException {
+    super.validate();
+
+    Preconditions.checkArgument(credentials != null, "\"credentials\" must not 
be null");
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java 
b/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
index d12b141ff..254de8c32 100644
--- a/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
+++ b/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
@@ -36,6 +36,8 @@ import org.apache.gravitino.authorization.Privileges;
 import org.apache.gravitino.authorization.Role;
 import org.apache.gravitino.authorization.SecurableObject;
 import org.apache.gravitino.authorization.User;
+import org.apache.gravitino.credential.Credential;
+import org.apache.gravitino.credential.CredentialFactory;
 import org.apache.gravitino.dto.AuditDTO;
 import org.apache.gravitino.dto.CatalogDTO;
 import org.apache.gravitino.dto.MetalakeDTO;
@@ -46,6 +48,7 @@ import org.apache.gravitino.dto.authorization.PrivilegeDTO;
 import org.apache.gravitino.dto.authorization.RoleDTO;
 import org.apache.gravitino.dto.authorization.SecurableObjectDTO;
 import org.apache.gravitino.dto.authorization.UserDTO;
+import org.apache.gravitino.dto.credential.CredentialDTO;
 import org.apache.gravitino.dto.file.FilesetDTO;
 import org.apache.gravitino.dto.messaging.TopicDTO;
 import org.apache.gravitino.dto.rel.ColumnDTO;
@@ -516,6 +519,31 @@ public class DTOConverters {
     return builder.build();
   }
 
+  /**
+   * Converts credentials to CredentialDTOs.
+   *
+   * @param credentials the credentials to be converted.
+   * @return The credential DTOs.
+   */
+  public static CredentialDTO[] toDTO(Credential[] credentials) {
+    return 
Arrays.stream(credentials).map(DTOConverters::toDTO).toArray(CredentialDTO[]::new);
+  }
+
+  /**
+   * Converts a Credential to a CredentialDTO.
+   *
+   * @param credential the credential to be converted.
+   * @return The credential DTO.
+   */
+  public static CredentialDTO toDTO(Credential credential) {
+    CredentialDTO.Builder builder =
+        CredentialDTO.builder()
+            .withCredentialType(credential.credentialType())
+            .withExpireTimeInMs(credential.expireTimeInMs())
+            .withCredentialInfo(credential.credentialInfo());
+    return builder.build();
+  }
+
   /**
    * Converts an Expression to an FunctionArg DTO.
    *
@@ -881,6 +909,30 @@ public class DTOConverters {
     return 
Arrays.stream(columns).map(DTOConverters::fromDTO).toArray(Column[]::new);
   }
 
+  /**
+   * Converts CredentialDTO array to credential array.
+   *
+   * @param credentials The credential DTO array to be converted.
+   * @return The credential array.
+   */
+  public static Credential[] fromDTO(CredentialDTO[] credentials) {
+    if (ArrayUtils.isEmpty(credentials)) {
+      return new Credential[0];
+    }
+    return 
Arrays.stream(credentials).map(DTOConverters::fromDTO).toArray(Credential[]::new);
+  }
+
+  /**
+   * Converts a CredentialDTO to a credential.
+   *
+   * @param credential The credential DTO to be converted.
+   * @return The credential.
+   */
+  public static Credential fromDTO(CredentialDTO credential) {
+    return CredentialFactory.create(
+        credential.credentialType(), credential.credentialInfo(), 
credential.expireTimeInMs());
+  }
+
   /**
    * Converts a ColumnDTO to a Column.
    *
diff --git 
a/common/src/test/java/org/apache/gravitino/credential/TestCredentialDTO.java 
b/common/src/test/java/org/apache/gravitino/credential/TestCredentialDTO.java
new file mode 100644
index 000000000..88fb394e8
--- /dev/null
+++ 
b/common/src/test/java/org/apache/gravitino/credential/TestCredentialDTO.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.credential;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import org.apache.gravitino.dto.credential.CredentialDTO;
+import org.apache.gravitino.json.JsonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestCredentialDTO {
+
+  @Test
+  public void testCredentialDTOSerDe() throws JsonProcessingException {
+    Map<String, String> credentialInfo =
+        ImmutableMap.of(
+            S3SecretKeyCredential.GRAVITINO_S3_STATIC_ACCESS_KEY_ID, 
"access-key",
+            S3SecretKeyCredential.GRAVITINO_S3_STATIC_SECRET_ACCESS_KEY, 
"secret-key");
+
+    CredentialDTO credentialDTO =
+        CredentialDTO.builder()
+            
.withCredentialType(S3SecretKeyCredential.S3_SECRET_KEY_CREDENTIAL_TYPE)
+            .withCredentialInfo(credentialInfo)
+            .withExpireTimeInMs(10)
+            .build();
+
+    String serJson = 
JsonUtils.objectMapper().writeValueAsString(credentialDTO);
+    CredentialDTO deserCredentialDTO =
+        JsonUtils.objectMapper().readValue(serJson, CredentialDTO.class);
+    Assertions.assertEquals(credentialDTO, deserCredentialDTO);
+  }
+}
diff --git 
a/common/src/test/java/org/apache/gravitino/credential/TestCredentialFactory.java
 
b/common/src/test/java/org/apache/gravitino/credential/TestCredentialFactory.java
new file mode 100644
index 000000000..f5a83e0d3
--- /dev/null
+++ 
b/common/src/test/java/org/apache/gravitino/credential/TestCredentialFactory.java
@@ -0,0 +1,116 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.gravitino.credential;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestCredentialFactory {
+
+  @Test
+  void testS3TokenCredential() {
+    Map<String, String> s3TokenCredentialInfo =
+        ImmutableMap.of(
+            S3TokenCredential.GRAVITINO_S3_SESSION_ACCESS_KEY_ID,
+            "accessKeyId",
+            S3TokenCredential.GRAVITINO_S3_SESSION_SECRET_ACCESS_KEY,
+            "secretAccessKey",
+            S3TokenCredential.GRAVITINO_S3_TOKEN,
+            "token");
+    long expireTime = 1000;
+    Credential s3TokenCredential =
+        CredentialFactory.create(
+            S3TokenCredential.S3_TOKEN_CREDENTIAL_TYPE, s3TokenCredentialInfo, 
expireTime);
+    Assertions.assertEquals(
+        S3TokenCredential.S3_TOKEN_CREDENTIAL_TYPE, 
s3TokenCredential.credentialType());
+    Assertions.assertTrue(s3TokenCredential instanceof S3TokenCredential);
+    S3TokenCredential s3TokenCredential1 = (S3TokenCredential) 
s3TokenCredential;
+    Assertions.assertEquals("accessKeyId", s3TokenCredential1.accessKeyId());
+    Assertions.assertEquals("secretAccessKey", 
s3TokenCredential1.secretAccessKey());
+    Assertions.assertEquals("token", s3TokenCredential1.sessionToken());
+    Assertions.assertEquals(expireTime, s3TokenCredential1.expireTimeInMs());
+  }
+
+  @Test
+  void testS3SecretKeyTokenCredential() {
+    Map<String, String> s3SecretKeyCredentialInfo =
+        ImmutableMap.of(
+            S3SecretKeyCredential.GRAVITINO_S3_STATIC_ACCESS_KEY_ID,
+            "accessKeyId",
+            S3SecretKeyCredential.GRAVITINO_S3_STATIC_SECRET_ACCESS_KEY,
+            "secretAccessKey");
+    long expireTime = 0;
+    Credential s3SecretKeyCredential =
+        CredentialFactory.create(
+            S3SecretKeyCredential.S3_SECRET_KEY_CREDENTIAL_TYPE,
+            s3SecretKeyCredentialInfo,
+            expireTime);
+    Assertions.assertEquals(
+        S3SecretKeyCredential.S3_SECRET_KEY_CREDENTIAL_TYPE,
+        s3SecretKeyCredential.credentialType());
+    Assertions.assertTrue(s3SecretKeyCredential instanceof 
S3SecretKeyCredential);
+    S3SecretKeyCredential s3SecretKeyCredential1 = (S3SecretKeyCredential) 
s3SecretKeyCredential;
+    Assertions.assertEquals("accessKeyId", 
s3SecretKeyCredential1.accessKeyId());
+    Assertions.assertEquals("secretAccessKey", 
s3SecretKeyCredential1.secretAccessKey());
+    Assertions.assertEquals(expireTime, 
s3SecretKeyCredential1.expireTimeInMs());
+  }
+
+  @Test
+  void testGcsTokenCredential() {
+    Map<String, String> gcsTokenCredentialInfo =
+        ImmutableMap.of(GCSTokenCredential.GCS_TOKEN_NAME, "accessToken");
+    long expireTime = 100;
+    Credential gcsTokenCredential =
+        CredentialFactory.create(
+            GCSTokenCredential.GCS_TOKEN_CREDENTIAL_TYPE, 
gcsTokenCredentialInfo, expireTime);
+    Assertions.assertEquals(
+        GCSTokenCredential.GCS_TOKEN_CREDENTIAL_TYPE, 
gcsTokenCredential.credentialType());
+    Assertions.assertTrue(gcsTokenCredential instanceof GCSTokenCredential);
+    GCSTokenCredential gcsTokenCredential1 = (GCSTokenCredential) 
gcsTokenCredential;
+    Assertions.assertEquals("accessToken", gcsTokenCredential1.token());
+    Assertions.assertEquals(expireTime, gcsTokenCredential1.expireTimeInMs());
+  }
+
+  @Test
+  void testOSSTokenCredential() {
+    Map<String, String> ossTokenCredentialInfo =
+        ImmutableMap.of(
+            OSSTokenCredential.GRAVITINO_OSS_SESSION_ACCESS_KEY_ID,
+            "access-id",
+            OSSTokenCredential.GRAVITINO_OSS_SESSION_SECRET_ACCESS_KEY,
+            "secret-key",
+            OSSTokenCredential.GRAVITINO_OSS_TOKEN,
+            "token");
+    long expireTime = 100;
+    Credential ossTokenCredential =
+        CredentialFactory.create(
+            OSSTokenCredential.OSS_TOKEN_CREDENTIAL_TYPE, 
ossTokenCredentialInfo, expireTime);
+    Assertions.assertEquals(
+        OSSTokenCredential.OSS_TOKEN_CREDENTIAL_TYPE, 
ossTokenCredential.credentialType());
+    Assertions.assertTrue(ossTokenCredential instanceof OSSTokenCredential);
+    OSSTokenCredential ossTokenCredential1 = (OSSTokenCredential) 
ossTokenCredential;
+    Assertions.assertEquals("access-id", ossTokenCredential1.accessKeyId());
+    Assertions.assertEquals("secret-key", 
ossTokenCredential1.secretAccessKey());
+    Assertions.assertEquals("token", ossTokenCredential1.securityToken());
+    Assertions.assertEquals(expireTime, ossTokenCredential1.expireTimeInMs());
+  }
+}
diff --git 
a/common/src/test/java/org/apache/gravitino/credential/TestCredentialPropertiesUtils.java
 
b/common/src/test/java/org/apache/gravitino/credential/TestCredentialPropertiesUtils.java
index 3b4571e01..8e52b1684 100644
--- 
a/common/src/test/java/org/apache/gravitino/credential/TestCredentialPropertiesUtils.java
+++ 
b/common/src/test/java/org/apache/gravitino/credential/TestCredentialPropertiesUtils.java
@@ -28,7 +28,7 @@ public class TestCredentialPropertiesUtils {
 
   @Test
   void testToIcebergProperties() {
-    S3TokenCredential s3TokenCredential = new S3TokenCredential("key", 
"secret", "token", 0);
+    S3TokenCredential s3TokenCredential = new S3TokenCredential("key", 
"secret", "token", 100);
     Map<String, String> icebergProperties =
         CredentialPropertyUtils.toIcebergProperties(s3TokenCredential);
     Map<String, String> expectedProperties =
diff --git 
a/core/src/test/java/org/apache/gravitino/credential/DummyCredentialProvider.java
 
b/core/src/test/java/org/apache/gravitino/credential/DummyCredentialProvider.java
index 864635e96..83516edee 100644
--- 
a/core/src/test/java/org/apache/gravitino/credential/DummyCredentialProvider.java
+++ 
b/core/src/test/java/org/apache/gravitino/credential/DummyCredentialProvider.java
@@ -23,6 +23,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import java.util.Set;
+import javax.ws.rs.NotSupportedException;
 import lombok.Getter;
 
 public class DummyCredentialProvider implements CredentialProvider {
@@ -79,5 +80,10 @@ public class DummyCredentialProvider implements 
CredentialProvider {
       return ImmutableMap.of(
           "writeLocation", writeLocations.toString(), "readLocation", 
readLocations.toString());
     }
+
+    @Override
+    public void initialize(Map<String, String> credentialInfo, long 
expireTimeInMs) {
+      throw new NotSupportedException();
+    }
   }
 }
diff --git 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/extension/DummyCredentialProvider.java
 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/extension/DummyCredentialProvider.java
index 6b1e4c087..5ed9d7b58 100644
--- 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/extension/DummyCredentialProvider.java
+++ 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/extension/DummyCredentialProvider.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 import javax.annotation.Nullable;
+import javax.ws.rs.NotSupportedException;
 import org.apache.gravitino.credential.Credential;
 import org.apache.gravitino.credential.CredentialContext;
 import org.apache.gravitino.credential.CredentialProvider;
@@ -45,6 +46,11 @@ public class DummyCredentialProvider implements 
CredentialProvider {
     public Map<String, String> credentialInfo() {
       return new HashMap<>();
     }
+
+    @Override
+    public void initialize(Map<String, String> credentialInfo, long 
expireTimeInMs) {
+      throw new NotSupportedException();
+    }
   }
 
   @Override

Reply via email to