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 545b22ded5 [#7145] feat(policy): implement the policy management on 
server-side (part-1) (#7821)
545b22ded5 is described below

commit 545b22ded5e1ebb92f9580db86b679ec6f3d7c86
Author: mchades <[email protected]>
AuthorDate: Fri Aug 8 10:07:09 2025 +0800

    [#7145] feat(policy): implement the policy management on server-side 
(part-1) (#7821)
    
    ### What changes were proposed in this pull request?
    
    implement the policy management on server-side
    
    ### Why are the changes needed?
    
    Fix: #7145
    
    ### Does this PR introduce _any_ user-facing change?
    
    yes, RESTFul APIs added
    
    ### How was this patch tested?
    
    tests addded
---
 .../org/apache/gravitino/policy/PolicyChange.java  |  26 +-
 .../gravitino/dto/policy/PolicyContentDTO.java     |  67 +++
 .../org/apache/gravitino/dto/policy/PolicyDTO.java | 298 ++++++++++
 .../dto/requests/PolicyCreateRequest.java          | 163 ++++++
 .../gravitino/dto/requests/PolicySetRequest.java   |  59 ++
 .../dto/requests/PolicyUpdateRequest.java          | 174 ++++++
 .../dto/requests/PolicyUpdatesRequest.java         |  57 ++
 .../dto/responses/PolicyListResponse.java          |  70 +++
 .../gravitino/dto/responses/PolicyResponse.java    |  61 +++
 .../apache/gravitino/dto/util/DTOConverters.java   |  77 +++
 .../apache/gravitino/dto/policy/TestPolicyDTO.java | 118 ++++
 .../dto/requests/TestPolicyCreateRequest.java      |  62 +++
 .../dto/requests/TestPolicyUpdatesRequest.java     |  94 ++++
 .../org/apache/gravitino/policy/PolicyManager.java |   5 +
 .../apache/gravitino/policy/TestPolicyManager.java |  11 +-
 .../apache/gravitino/server/GravitinoServer.java   |   2 +
 .../server/web/rest/ExceptionHandlers.java         |  45 ++
 .../server/web/rest/FilesetOperations.java         |   2 +-
 .../server/web/rest/PolicyOperations.java          | 267 +++++++++
 .../gravitino/server/web/rest/TableOperations.java |   2 +-
 .../gravitino/server/web/rest/TagOperations.java   |   2 +-
 .../gravitino/server/web/rest/TopicOperations.java |   2 +-
 .../server/web/rest/TestPolicyOperations.java      | 597 +++++++++++++++++++++
 23 files changed, 2249 insertions(+), 12 deletions(-)

diff --git a/api/src/main/java/org/apache/gravitino/policy/PolicyChange.java 
b/api/src/main/java/org/apache/gravitino/policy/PolicyChange.java
index 297572b076..433f441b62 100644
--- a/api/src/main/java/org/apache/gravitino/policy/PolicyChange.java
+++ b/api/src/main/java/org/apache/gravitino/policy/PolicyChange.java
@@ -51,11 +51,12 @@ public interface PolicyChange {
   /**
    * Creates a new policy change to update the content of the policy.
    *
+   * @param policyType The type of the policy, used for validation.
    * @param content The new content for the policy.
    * @return The policy change.
    */
-  static PolicyChange updateContent(PolicyContent content) {
-    return new UpdateContent(content);
+  static PolicyChange updateContent(String policyType, PolicyContent content) {
+    return new UpdateContent(policyType, content);
   }
 
   /** A policy change to rename the policy. */
@@ -160,12 +161,23 @@ public interface PolicyChange {
 
   /** A policy change to update the content of the policy. */
   final class UpdateContent implements PolicyChange {
+    private final String policyType;
     private final PolicyContent content;
 
-    private UpdateContent(PolicyContent content) {
+    private UpdateContent(String policyType, PolicyContent content) {
+      this.policyType = policyType;
       this.content = content;
     }
 
+    /**
+     * Get the type of the policy.
+     *
+     * @return Get the type of the policy.
+     */
+    public String getPolicyType() {
+      return policyType;
+    }
+
     /**
      * Get the content of the policy change.
      *
@@ -177,19 +189,19 @@ public interface PolicyChange {
 
     @Override
     public boolean equals(Object o) {
-      if (o == null || getClass() != o.getClass()) return false;
+      if (!(o instanceof UpdateContent)) return false;
       UpdateContent that = (UpdateContent) o;
-      return Objects.equals(content, that.content);
+      return Objects.equals(policyType, that.policyType) && 
Objects.equals(content, that.content);
     }
 
     @Override
     public int hashCode() {
-      return Objects.hash(content);
+      return Objects.hash(policyType, content);
     }
 
     @Override
     public String toString() {
-      return "UPDATE CONTENT " + content;
+      return "UPDATE POLICY CONTENT " + "policyType=" + policyType + ", 
content=" + content;
     }
   }
 }
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/policy/PolicyContentDTO.java 
b/common/src/main/java/org/apache/gravitino/dto/policy/PolicyContentDTO.java
new file mode 100644
index 0000000000..505881430a
--- /dev/null
+++ b/common/src/main/java/org/apache/gravitino/dto/policy/PolicyContentDTO.java
@@ -0,0 +1,67 @@
+/*
+ * 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.policy;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Map;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.apache.gravitino.policy.PolicyContent;
+
+/** Represents a Policy Content Data Transfer Object (DTO). */
+public interface PolicyContentDTO extends PolicyContent {
+
+  /** Represents a custom policy content DTO. */
+  @EqualsAndHashCode
+  @ToString
+  @Builder(setterPrefix = "with")
+  @AllArgsConstructor(access = lombok.AccessLevel.PRIVATE)
+  class CustomContentDTO implements PolicyContentDTO {
+
+    @JsonProperty("customRules")
+    private Map<String, Object> customRules;
+
+    @JsonProperty("properties")
+    private Map<String, String> properties;
+
+    // Default constructor for Jackson deserialization only.
+    private CustomContentDTO() {}
+
+    /**
+     * Returns the custom rules defined in this policy content.
+     *
+     * @return a map of custom rules where the key is the rule name and the 
value is the rule value.
+     */
+    public Map<String, Object> customRules() {
+      return customRules;
+    }
+
+    @Override
+    public Map<String, String> properties() {
+      return properties;
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      // no validation needed for custom content
+    }
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/policy/PolicyDTO.java 
b/common/src/main/java/org/apache/gravitino/dto/policy/PolicyDTO.java
new file mode 100644
index 0000000000..520950d681
--- /dev/null
+++ b/common/src/main/java/org/apache/gravitino/dto/policy/PolicyDTO.java
@@ -0,0 +1,298 @@
+/*
+ * 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.policy;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.google.common.base.Preconditions;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import lombok.ToString;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.dto.AuditDTO;
+import org.apache.gravitino.policy.Policy;
+
+/** Represents a Policy Data Transfer Object (DTO). */
+@ToString
+public class PolicyDTO implements Policy {
+
+  @JsonProperty("name")
+  private String name;
+
+  @JsonProperty("comment")
+  private String comment;
+
+  @JsonProperty("policyType")
+  private String policyType;
+
+  @JsonProperty("enabled")
+  private boolean enabled;
+
+  @JsonProperty("exclusive")
+  private boolean exclusive;
+
+  @JsonProperty("inheritable")
+  private boolean inheritable;
+
+  @JsonProperty("supportedObjectTypes")
+  private Set<MetadataObject.Type> supportedObjectTypes = 
Collections.emptySet();
+
+  @JsonProperty("content")
+  @JsonTypeInfo(
+      use = JsonTypeInfo.Id.NAME,
+      include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
+      property = "policyType",
+      defaultImpl = PolicyContentDTO.CustomContentDTO.class)
+  @JsonSubTypes({
+    // add mappings for built-in types here
+    // For example: @JsonSubTypes.Type(value = DataCompactionContent.class, 
name =
+    // "system_data_compaction")
+  })
+  private PolicyContentDTO content;
+
+  @JsonProperty("inherited")
+  private Optional<Boolean> inherited = Optional.empty();
+
+  @JsonProperty("audit")
+  private AuditDTO audit;
+
+  private PolicyDTO() {}
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof PolicyDTO)) return false;
+    PolicyDTO policyDTO = (PolicyDTO) o;
+    return enabled == policyDTO.enabled
+        && exclusive == policyDTO.exclusive
+        && inheritable == policyDTO.inheritable
+        && Objects.equals(name, policyDTO.name)
+        && Objects.equals(comment, policyDTO.comment)
+        && Objects.equals(policyType, policyDTO.policyType)
+        && Objects.equals(supportedObjectTypes, policyDTO.supportedObjectTypes)
+        && Objects.equals(content, policyDTO.content)
+        && Objects.equals(audit, policyDTO.audit);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(
+        name,
+        comment,
+        policyType,
+        enabled,
+        exclusive,
+        inheritable,
+        supportedObjectTypes,
+        content,
+        audit);
+  }
+
+  /** @return a new builder for constructing a PolicyDTO. */
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  public String name() {
+    return name;
+  }
+
+  @Override
+  public String policyType() {
+    return policyType;
+  }
+
+  @Override
+  public String comment() {
+    return comment;
+  }
+
+  @Override
+  public boolean enabled() {
+    return enabled;
+  }
+
+  @Override
+  public boolean exclusive() {
+    return exclusive;
+  }
+
+  @Override
+  public boolean inheritable() {
+    return inheritable;
+  }
+
+  @Override
+  public Set<MetadataObject.Type> supportedObjectTypes() {
+    return supportedObjectTypes;
+  }
+
+  @Override
+  public PolicyContentDTO content() {
+    return content;
+  }
+
+  @Override
+  public Optional<Boolean> inherited() {
+    return inherited;
+  }
+
+  @Override
+  public AuditDTO auditInfo() {
+    return audit;
+  }
+
+  /** Builder class for constructing PolicyDTO instances. */
+  public static class Builder {
+    private final PolicyDTO policyDTO;
+
+    private Builder() {
+      policyDTO = new PolicyDTO();
+    }
+
+    /**
+     * Sets the name of the policy.
+     *
+     * @param name The name of the policy.
+     * @return The builder instance.
+     */
+    public Builder withName(String name) {
+      policyDTO.name = name;
+      return this;
+    }
+
+    /**
+     * Sets the comment associated with the policy.
+     *
+     * @param comment The comment associated with the policy.
+     * @return The builder instance.
+     */
+    public Builder withComment(String comment) {
+      policyDTO.comment = comment;
+      return this;
+    }
+
+    /**
+     * Sets the type of the policy.
+     *
+     * @param policyType The type of the policy.
+     * @return The builder instance.
+     */
+    public Builder withPolicyType(String policyType) {
+      policyDTO.policyType = policyType;
+      return this;
+    }
+
+    /**
+     * Sets whether the policy is enabled or not.
+     *
+     * @param enabled Whether the policy is enabled.
+     * @return The builder instance.
+     */
+    public Builder withEnabled(boolean enabled) {
+      policyDTO.enabled = enabled;
+      return this;
+    }
+
+    /**
+     * Sets whether the policy is exclusive or not.
+     *
+     * @param exclusive Whether the policy is exclusive.
+     * @return The builder instance.
+     */
+    public Builder withExclusive(boolean exclusive) {
+      policyDTO.exclusive = exclusive;
+      return this;
+    }
+
+    /**
+     * Sets whether the policy is inheritable or not.
+     *
+     * @param inheritable Whether the policy is inheritable.
+     * @return The builder instance.
+     */
+    public Builder withInheritable(boolean inheritable) {
+      policyDTO.inheritable = inheritable;
+      return this;
+    }
+
+    /**
+     * Sets the set of supported metadata object types for the policy.
+     *
+     * @param supportedObjectTypes The set of supported metadata object types.
+     * @return The builder instance.
+     */
+    public Builder withSupportedObjectTypes(Set<MetadataObject.Type> 
supportedObjectTypes) {
+      policyDTO.supportedObjectTypes = supportedObjectTypes;
+      return this;
+    }
+
+    /**
+     * Sets the content of the policy.
+     *
+     * @param content The content of the policy.
+     * @return The builder instance.
+     */
+    public Builder withContent(PolicyContentDTO content) {
+      policyDTO.content = content;
+      return this;
+    }
+
+    /**
+     * Sets the audit information for the policy.
+     *
+     * @param audit The audit information for the policy.
+     * @return The builder instance.
+     */
+    public Builder withAudit(AuditDTO audit) {
+      policyDTO.audit = audit;
+      return this;
+    }
+
+    /**
+     * Sets whether the policy is inherited.
+     *
+     * @param inherited Whether the policy is inherited.
+     * @return The builder instance.
+     */
+    public Builder withInherited(Optional<Boolean> inherited) {
+      policyDTO.inherited = inherited;
+      return this;
+    }
+
+    /** @return The constructed Policy DTO. */
+    public PolicyDTO build() {
+      Preconditions.checkArgument(
+          StringUtils.isNotBlank(policyDTO.name), "policy name cannot be 
empty");
+      Preconditions.checkArgument(
+          StringUtils.isNotBlank(policyDTO.policyType), "policy type cannot be 
empty");
+      Preconditions.checkArgument(policyDTO.content != null, "policy content 
cannot be null");
+      Preconditions.checkArgument(
+          CollectionUtils.isNotEmpty(policyDTO.supportedObjectTypes),
+          "supported objectTypes cannot be empty");
+      policyDTO.content.validate();
+      return policyDTO;
+    }
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/requests/PolicyCreateRequest.java
 
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyCreateRequest.java
new file mode 100644
index 0000000000..9b57050326
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyCreateRequest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.requests;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.google.common.base.Preconditions;
+import java.util.Set;
+import javax.annotation.Nullable;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.dto.policy.PolicyContentDTO;
+import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Represents a request to create a policy. */
+@Getter
+@EqualsAndHashCode
+@ToString
+public class PolicyCreateRequest implements RESTRequest {
+
+  @JsonProperty("name")
+  private final String name;
+
+  @JsonProperty("comment")
+  @Nullable
+  private final String comment;
+
+  @JsonProperty("policyType")
+  private final String policyType;
+
+  @JsonProperty(value = "enabled", defaultValue = "true")
+  private final Boolean enabled;
+
+  @JsonProperty("content")
+  @JsonTypeInfo(
+      use = JsonTypeInfo.Id.NAME,
+      include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
+      property = "policyType",
+      defaultImpl = PolicyContentDTO.CustomContentDTO.class)
+  @JsonSubTypes({
+    // add mappings for built-in types here
+    // For example: @JsonSubTypes.Type(value = DataCompactionContent.class, 
name =
+    // "system_data_compaction")
+  })
+  private final PolicyContentDTO policyContent;
+
+  @JsonProperty("exclusive")
+  private final Boolean exclusive;
+
+  @JsonProperty("inheritable")
+  private final Boolean inheritable;
+
+  @JsonProperty("supportedObjectTypes")
+  private final Set<MetadataObject.Type> supportedObjectTypes;
+
+  /**
+   * Creates a new PolicyCreateRequest.
+   *
+   * @param name The name of the policy.
+   * @param comment The comment of the policy.
+   * @param type The type of the policy.
+   * @param enabled Whether the policy is enabled.
+   * @param exclusive Whether the policy is exclusive.
+   * @param inheritable Whether the policy is inheritable.
+   * @param supportedObjectTypes The set of metadata object types that the 
policy can be applied to.
+   * @param content The content of the policy.
+   */
+  public PolicyCreateRequest(
+      String name,
+      String type,
+      String comment,
+      boolean enabled,
+      boolean exclusive,
+      boolean inheritable,
+      Set<MetadataObject.Type> supportedObjectTypes,
+      PolicyContentDTO content) {
+    this.name = name;
+    this.policyType = type;
+    this.comment = comment;
+    this.enabled = enabled;
+    this.exclusive = exclusive;
+    this.inheritable = inheritable;
+    this.supportedObjectTypes = supportedObjectTypes;
+    this.policyContent = content;
+  }
+
+  /** This is the constructor that is used by Jackson deserializer */
+  private PolicyCreateRequest() {
+    this(null, null, null, true, false, false, null, null);
+  }
+
+  /**
+   * Validates the request.
+   *
+   * @throws IllegalArgumentException If the request is invalid, this 
exception is thrown.
+   */
+  @Override
+  public void validate() throws IllegalArgumentException {
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(name), "\"name\" is required and cannot be 
empty");
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(policyType), "\"policyType\" is required and 
cannot be empty");
+
+    Policy.BuiltInType builtInType = 
Policy.BuiltInType.fromPolicyType(policyType);
+    if (builtInType != Policy.BuiltInType.CUSTOM) {
+      if (exclusive != null) {
+        Preconditions.checkArgument(
+            exclusive.equals(builtInType.exclusive()),
+            String.format(
+                "Exclusive flag for built-in policy type '%s' must be %s",
+                builtInType, builtInType.exclusive()));
+      }
+
+      if (inheritable != null) {
+        Preconditions.checkArgument(
+            inheritable.equals(builtInType.inheritable()),
+            String.format(
+                "Inheritable flag for built-in policy type '%s' must be %s",
+                builtInType, builtInType.inheritable()));
+      }
+
+      if (supportedObjectTypes != null) {
+        Preconditions.checkArgument(
+            builtInType.supportedObjectTypes().equals(supportedObjectTypes),
+            String.format(
+                "Supported object types for built-in policy type '%s' must be 
%s",
+                builtInType, builtInType.supportedObjectTypes()));
+      }
+    } else {
+      Preconditions.checkArgument(
+          exclusive != null, "\"exclusive\" is required for custom policy 
type");
+      Preconditions.checkArgument(
+          inheritable != null, "\"inheritable\" is required for custom policy 
type");
+      Preconditions.checkArgument(
+          supportedObjectTypes != null,
+          "\"supportedObjectTypes\" is required for custom policy type");
+    }
+    Preconditions.checkArgument(
+        policyContent != null, "\"content\" is required and cannot be null");
+    policyContent.validate();
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/requests/PolicySetRequest.java 
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicySetRequest.java
new file mode 100644
index 0000000000..bb2c05c02a
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicySetRequest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.requests;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Represents a request to set a policy in use. */
+@Getter
+@EqualsAndHashCode
+@ToString
+public class PolicySetRequest implements RESTRequest {
+
+  @JsonProperty("enable")
+  private final boolean enable;
+
+  /** Default constructor for PolicySetRequest. */
+  public PolicySetRequest() {
+    this(false);
+  }
+
+  /**
+   * Constructor for PolicySetRequest.
+   *
+   * @param enable The enable status to set.
+   */
+  public PolicySetRequest(boolean enable) {
+    this.enable = enable;
+  }
+
+  /**
+   * Validates the request. No validation needed.
+   *
+   * @throws IllegalArgumentException If the request is invalid.
+   */
+  @Override
+  public void validate() throws IllegalArgumentException {
+    // No validation needed
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdateRequest.java
 
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdateRequest.java
new file mode 100644
index 0000000000..f33236e84f
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdateRequest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.requests;
+
+import static org.apache.gravitino.dto.util.DTOConverters.fromDTO;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.google.common.base.Preconditions;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.dto.policy.PolicyContentDTO;
+import org.apache.gravitino.policy.PolicyChange;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Represents a request to update a policy. */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
+@JsonSubTypes({
+  @JsonSubTypes.Type(value = PolicyUpdateRequest.RenamePolicyRequest.class, 
name = "rename"),
+  @JsonSubTypes.Type(
+      value = PolicyUpdateRequest.UpdatePolicyCommentRequest.class,
+      name = "updateComment"),
+  @JsonSubTypes.Type(
+      value = PolicyUpdateRequest.UpdatePolicyContentRequest.class,
+      name = "updateContent")
+})
+public interface PolicyUpdateRequest extends RESTRequest {
+
+  /**
+   * Returns the policy change.
+   *
+   * @return the policy change.
+   */
+  PolicyChange policyChange();
+
+  /** The policy update request for renaming a policy. */
+  @EqualsAndHashCode
+  @Getter
+  @ToString
+  class RenamePolicyRequest implements PolicyUpdateRequest {
+
+    @JsonProperty("newName")
+    private final String newName;
+
+    /**
+     * Creates a new RenamePolicyRequest.
+     *
+     * @param newName The new name of the policy.
+     */
+    public RenamePolicyRequest(String newName) {
+      this.newName = newName;
+    }
+
+    /** This is the constructor that is used by Jackson deserializer */
+    private RenamePolicyRequest() {
+      this(null);
+    }
+
+    @Override
+    public PolicyChange policyChange() {
+      return PolicyChange.rename(newName);
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      Preconditions.checkArgument(StringUtils.isNotBlank(newName), 
"\"newName\" must not be blank");
+    }
+  }
+
+  /** The policy update request for updating a policy comment. */
+  @EqualsAndHashCode
+  @ToString
+  @Getter
+  class UpdatePolicyCommentRequest implements PolicyUpdateRequest {
+
+    @JsonProperty("newComment")
+    private final String newComment;
+
+    /**
+     * Creates a new UpdatePolicyCommentRequest.
+     *
+     * @param newComment The new comment of the policy.
+     */
+    public UpdatePolicyCommentRequest(String newComment) {
+      this.newComment = newComment;
+    }
+
+    /** This is the constructor that is used by Jackson deserializer */
+    private UpdatePolicyCommentRequest() {
+      this(null);
+    }
+
+    @Override
+    public PolicyChange policyChange() {
+      return PolicyChange.updateComment(newComment);
+    }
+
+    /** Validates the fields of the request. Always pass. */
+    @Override
+    public void validate() throws IllegalArgumentException {}
+  }
+
+  /** The policy update request for updating a policy content. */
+  @EqualsAndHashCode
+  @Getter
+  @ToString
+  class UpdatePolicyContentRequest implements PolicyUpdateRequest {
+
+    @JsonProperty("policyType")
+    private final String policyType;
+
+    @JsonProperty("newContent")
+    @JsonTypeInfo(
+        use = JsonTypeInfo.Id.NAME,
+        include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
+        property = "policyType",
+        defaultImpl = PolicyContentDTO.CustomContentDTO.class)
+    @JsonSubTypes({
+      // add mappings for built-in types here
+      // For example: @JsonSubTypes.Type(value = DataCompactionContent.class, 
name =
+      // "system_data_compaction")
+    })
+    private final PolicyContentDTO newContent;
+
+    /**
+     * Creates a new UpdatePolicyContentRequest.
+     *
+     * @param policyType The type of the policy, used for validation and 
serialization. This should
+     *     match the type of the content being updated.
+     * @param newContent The new content of the policy.
+     */
+    public UpdatePolicyContentRequest(String policyType, PolicyContentDTO 
newContent) {
+      this.policyType = policyType;
+      this.newContent = newContent;
+    }
+
+    /** This is the constructor that is used by Jackson deserializer */
+    private UpdatePolicyContentRequest() {
+      this(null, null);
+    }
+
+    @Override
+    public PolicyChange policyChange() {
+      return PolicyChange.updateContent(policyType, fromDTO(newContent));
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      Preconditions.checkArgument(newContent != null, "\"newContent\" must not 
be null");
+      newContent.validate();
+    }
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdatesRequest.java
 
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdatesRequest.java
new file mode 100644
index 0000000000..4b529895ec
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdatesRequest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.requests;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import java.util.List;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Represents a request to update a policy. */
+@Getter
+@EqualsAndHashCode
+@ToString
+public class PolicyUpdatesRequest implements RESTRequest {
+
+  @JsonProperty("updates")
+  private final List<PolicyUpdateRequest> updates;
+
+  /**
+   * Creates a new PolicyUpdatesRequest.
+   *
+   * @param updates The updates to apply to the policy.
+   */
+  public PolicyUpdatesRequest(List<PolicyUpdateRequest> updates) {
+    this.updates = updates;
+  }
+
+  /** This is the constructor that is used by Jackson deserializer */
+  public PolicyUpdatesRequest() {
+    this(null);
+  }
+
+  @Override
+  public void validate() throws IllegalArgumentException {
+    Preconditions.checkArgument(updates != null, "updates must not be null");
+    updates.forEach(RESTRequest::validate);
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/responses/PolicyListResponse.java
 
b/common/src/main/java/org/apache/gravitino/dto/responses/PolicyListResponse.java
new file mode 100644
index 0000000000..a260171c79
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/responses/PolicyListResponse.java
@@ -0,0 +1,70 @@
+/*
+ * 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 java.util.Arrays;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.gravitino.dto.policy.PolicyDTO;
+
+/** Represents a response for a list of policies. */
+@Getter
+@ToString
+@EqualsAndHashCode(callSuper = true)
+public class PolicyListResponse extends BaseResponse {
+
+  @JsonProperty("policies")
+  private final PolicyDTO[] policies;
+
+  /**
+   * Creates a new PolicyListResponse.
+   *
+   * @param policies The list of policies.
+   */
+  public PolicyListResponse(PolicyDTO[] policies) {
+    super(0);
+    this.policies = policies;
+  }
+
+  /**
+   * This is the constructor that is used by Jackson deserializer to create an 
instance of
+   * PolicyListResponse.
+   */
+  public PolicyListResponse() {
+    super();
+    this.policies = null;
+  }
+
+  @Override
+  public void validate() throws IllegalArgumentException {
+    super.validate();
+
+    Preconditions.checkArgument(policies != null, "\"policies\" must not be 
null");
+    Arrays.stream(policies)
+        .forEach(
+            t -> {
+              Preconditions.checkArgument(t != null, "policy must not be 
null");
+              Preconditions.checkArgument(t.content() != null, "policy content 
must not be null");
+              t.content().validate();
+            });
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/responses/PolicyResponse.java 
b/common/src/main/java/org/apache/gravitino/dto/responses/PolicyResponse.java
new file mode 100644
index 0000000000..92645e5d42
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/responses/PolicyResponse.java
@@ -0,0 +1,61 @@
+/*
+ * 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.policy.PolicyDTO;
+
+/** Represents a response for a policy. */
+@Getter
+@ToString
+@EqualsAndHashCode(callSuper = true)
+public class PolicyResponse extends BaseResponse {
+
+  @JsonProperty("policy")
+  private final PolicyDTO policy;
+
+  /**
+   * Creates a new PolicyResponse.
+   *
+   * @param policy The policy.
+   */
+  public PolicyResponse(PolicyDTO policy) {
+    super(0);
+    this.policy = policy;
+  }
+
+  /**
+   * This is the constructor that is used by Jackson deserializer to create an 
instance of
+   * PolicyResponse.
+   */
+  private PolicyResponse() {
+    this(null);
+  }
+
+  @Override
+  public void validate() throws IllegalArgumentException {
+    super.validate();
+
+    Preconditions.checkArgument(policy != null, "\"policy\" 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 7c12cf29a8..020523bf44 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
@@ -57,6 +57,8 @@ import org.apache.gravitino.dto.job.SparkJobTemplateDTO;
 import org.apache.gravitino.dto.messaging.TopicDTO;
 import org.apache.gravitino.dto.model.ModelDTO;
 import org.apache.gravitino.dto.model.ModelVersionDTO;
+import org.apache.gravitino.dto.policy.PolicyContentDTO;
+import org.apache.gravitino.dto.policy.PolicyDTO;
 import org.apache.gravitino.dto.rel.ColumnDTO;
 import org.apache.gravitino.dto.rel.DistributionDTO;
 import org.apache.gravitino.dto.rel.SortOrderDTO;
@@ -92,6 +94,9 @@ import org.apache.gravitino.job.SparkJobTemplate;
 import org.apache.gravitino.messaging.Topic;
 import org.apache.gravitino.model.Model;
 import org.apache.gravitino.model.ModelVersion;
+import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyContent;
+import org.apache.gravitino.policy.PolicyContents;
 import org.apache.gravitino.rel.Column;
 import org.apache.gravitino.rel.Table;
 import org.apache.gravitino.rel.expressions.Expression;
@@ -531,6 +536,57 @@ public class DTOConverters {
     return builder.build();
   }
 
+  /**
+   * Converts a Policy to a PolicyDTO.
+   *
+   * @param policy The policy to be converted.
+   * @param inherited The inherited flag.
+   * @return The policy DTO.
+   */
+  public static PolicyDTO toDTO(Policy policy, Optional<Boolean> inherited) {
+    PolicyDTO.Builder builder =
+        PolicyDTO.builder()
+            .withName(policy.name())
+            .withComment(policy.comment())
+            .withPolicyType(policy.policyType())
+            .withEnabled(policy.enabled())
+            .withExclusive(policy.exclusive())
+            .withInheritable(policy.inheritable())
+            .withSupportedObjectTypes(policy.supportedObjectTypes())
+            .withContent(toDTO(policy.content()))
+            .withInherited(inherited)
+            .withAudit(toDTO(policy.auditInfo()))
+            .withInherited(inherited);
+
+    return builder.build();
+  }
+
+  /**
+   * Converts a PolicyContent to a PolicyContentDTO.
+   *
+   * @param policyContent The policyContent to be converted.
+   * @return The policy content DTO.
+   */
+  public static PolicyContentDTO toDTO(PolicyContent policyContent) {
+    if (policyContent == null) {
+      return null;
+    }
+
+    if (policyContent instanceof PolicyContentDTO) {
+      return (PolicyContentDTO) policyContent;
+    }
+
+    if (policyContent instanceof PolicyContents.CustomContent) {
+      PolicyContents.CustomContent customContent = 
(PolicyContents.CustomContent) policyContent;
+      return PolicyContentDTO.CustomContentDTO.builder()
+          .withCustomRules(customContent.customRules())
+          .withProperties(customContent.properties())
+          .build();
+    }
+
+    throw new IllegalArgumentException("Unsupported policy content: " + 
policyContent);
+  }
+
   /**
    * Converts credentials to CredentialDTOs.
    *
@@ -1188,4 +1244,25 @@ public class DTOConverters {
             "Unsupported job template type: " + jobTemplateDTO.jobType());
     }
   }
+
+  /**
+   * Converts a PolicyContentDTO to a PolicyContent.
+   *
+   * @param policyContentDTO The policy content DTO to be converted.
+   * @return The policy content.
+   */
+  public static PolicyContent fromDTO(PolicyContentDTO policyContentDTO) {
+    if (policyContentDTO == null) {
+      return null;
+    }
+
+    if (policyContentDTO instanceof PolicyContentDTO.CustomContentDTO) {
+      PolicyContentDTO.CustomContentDTO customContentDTO =
+          (PolicyContentDTO.CustomContentDTO) policyContentDTO;
+      return PolicyContents.custom(customContentDTO.customRules(), 
customContentDTO.properties());
+    }
+
+    throw new IllegalArgumentException(
+        "Unsupported policy content type: " + policyContentDTO.getClass());
+  }
 }
diff --git 
a/common/src/test/java/org/apache/gravitino/dto/policy/TestPolicyDTO.java 
b/common/src/test/java/org/apache/gravitino/dto/policy/TestPolicyDTO.java
new file mode 100644
index 0000000000..ebb173409e
--- /dev/null
+++ b/common/src/test/java/org/apache/gravitino/dto/policy/TestPolicyDTO.java
@@ -0,0 +1,118 @@
+/*
+ * 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.policy;
+
+import static org.apache.gravitino.policy.Policy.SUPPORTS_ALL_OBJECT_TYPES;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.collect.ImmutableMap;
+import java.time.Instant;
+import java.util.Optional;
+import org.apache.gravitino.dto.AuditDTO;
+import org.apache.gravitino.json.JsonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestPolicyDTO {
+
+  @Test
+  public void testPolicySerDe() throws JsonProcessingException {
+    AuditDTO audit = 
AuditDTO.builder().withCreator("user1").withCreateTime(Instant.now()).build();
+    PolicyContentDTO.CustomContentDTO customContent =
+        PolicyContentDTO.CustomContentDTO.builder()
+            .withCustomRules(ImmutableMap.of("key1", "value1"))
+            .withProperties(ImmutableMap.of("prop1", "value1"))
+            .build();
+
+    PolicyDTO policyDTO =
+        PolicyDTO.builder()
+            .withName("policy_test")
+            .withComment("policy comment")
+            .withPolicyType("my_compaction")
+            .withEnabled(true)
+            .withInheritable(true)
+            .withExclusive(true)
+            .withInheritable(true)
+            .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+            .withContent(customContent)
+            .withAudit(audit)
+            .build();
+
+    String serJson = JsonUtils.objectMapper().writeValueAsString(policyDTO);
+    PolicyDTO deserPolicyDTO = JsonUtils.objectMapper().readValue(serJson, 
PolicyDTO.class);
+    Assertions.assertEquals(policyDTO, deserPolicyDTO);
+
+    Assertions.assertEquals("policy_test", deserPolicyDTO.name());
+    Assertions.assertEquals("policy comment", deserPolicyDTO.comment());
+    Assertions.assertEquals("my_compaction", deserPolicyDTO.policyType());
+    Assertions.assertTrue(deserPolicyDTO.enabled());
+    Assertions.assertTrue(deserPolicyDTO.inheritable());
+    Assertions.assertTrue(deserPolicyDTO.exclusive());
+    Assertions.assertEquals(SUPPORTS_ALL_OBJECT_TYPES, 
deserPolicyDTO.supportedObjectTypes());
+    Assertions.assertEquals(customContent, deserPolicyDTO.content());
+    Assertions.assertEquals(audit, deserPolicyDTO.auditInfo());
+
+    // Test policy with inherited
+    PolicyDTO policyDTO2 =
+        PolicyDTO.builder()
+            .withName("policy_test")
+            .withComment("policy comment")
+            .withPolicyType("my_compaction")
+            .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+            .withContent(customContent)
+            .withAudit(audit)
+            .withInherited(Optional.empty())
+            .build();
+
+    serJson = JsonUtils.objectMapper().writeValueAsString(policyDTO2);
+    PolicyDTO deserPolicyDTO2 = JsonUtils.objectMapper().readValue(serJson, 
PolicyDTO.class);
+    Assertions.assertEquals(policyDTO2, deserPolicyDTO2);
+    Assertions.assertEquals(Optional.empty(), deserPolicyDTO2.inherited());
+
+    PolicyDTO policyDTO3 =
+        PolicyDTO.builder()
+            .withName("policy_test")
+            .withComment("policy comment")
+            .withPolicyType("my_compaction")
+            .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+            .withContent(customContent)
+            .withAudit(audit)
+            .withInherited(Optional.of(false))
+            .build();
+
+    serJson = JsonUtils.objectMapper().writeValueAsString(policyDTO3);
+    PolicyDTO deserPolicyDTO3 = JsonUtils.objectMapper().readValue(serJson, 
PolicyDTO.class);
+    Assertions.assertEquals(Optional.of(false), deserPolicyDTO3.inherited());
+
+    PolicyDTO policyDTO4 =
+        PolicyDTO.builder()
+            .withName("policy_test")
+            .withComment("policy comment")
+            .withPolicyType("my_compaction")
+            .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+            .withContent(customContent)
+            .withAudit(audit)
+            .withInherited(Optional.of(true))
+            .build();
+
+    serJson = JsonUtils.objectMapper().writeValueAsString(policyDTO4);
+    PolicyDTO deserPolicyDTO4 = JsonUtils.objectMapper().readValue(serJson, 
PolicyDTO.class);
+    Assertions.assertEquals(Optional.of(true), deserPolicyDTO4.inherited());
+  }
+}
diff --git 
a/common/src/test/java/org/apache/gravitino/dto/requests/TestPolicyCreateRequest.java
 
b/common/src/test/java/org/apache/gravitino/dto/requests/TestPolicyCreateRequest.java
new file mode 100644
index 0000000000..1551719833
--- /dev/null
+++ 
b/common/src/test/java/org/apache/gravitino/dto/requests/TestPolicyCreateRequest.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.requests;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.collect.ImmutableMap;
+import org.apache.gravitino.dto.policy.PolicyContentDTO;
+import org.apache.gravitino.json.JsonUtils;
+import org.apache.gravitino.policy.Policy;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestPolicyCreateRequest {
+
+  @Test
+  public void testPolicyCreateRequestSerDe() throws JsonProcessingException {
+    PolicyContentDTO content =
+        PolicyContentDTO.CustomContentDTO.builder()
+            .withCustomRules(ImmutableMap.of("rule1", "value1"))
+            .withProperties(null)
+            .build();
+    PolicyCreateRequest request =
+        new PolicyCreateRequest(
+            "policy_test",
+            "test_type",
+            "policy comment",
+            true,
+            true,
+            true,
+            Policy.SUPPORTS_ALL_OBJECT_TYPES,
+            content);
+    String serJson = JsonUtils.objectMapper().writeValueAsString(request);
+    PolicyCreateRequest deserRequest =
+        JsonUtils.objectMapper().readValue(serJson, PolicyCreateRequest.class);
+    Assertions.assertEquals(request, deserRequest);
+    Assertions.assertEquals("policy_test", deserRequest.getName());
+    Assertions.assertEquals("policy comment", deserRequest.getComment());
+    Assertions.assertEquals("test_type", deserRequest.getPolicyType());
+    Assertions.assertTrue(deserRequest.getEnabled());
+    Assertions.assertTrue(deserRequest.getExclusive());
+    Assertions.assertTrue(deserRequest.getInheritable());
+    Assertions.assertEquals(
+        Policy.SUPPORTS_ALL_OBJECT_TYPES, 
deserRequest.getSupportedObjectTypes());
+    Assertions.assertEquals(content, deserRequest.getPolicyContent());
+  }
+}
diff --git 
a/common/src/test/java/org/apache/gravitino/dto/requests/TestPolicyUpdatesRequest.java
 
b/common/src/test/java/org/apache/gravitino/dto/requests/TestPolicyUpdatesRequest.java
new file mode 100644
index 0000000000..98da580afe
--- /dev/null
+++ 
b/common/src/test/java/org/apache/gravitino/dto/requests/TestPolicyUpdatesRequest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.requests;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.List;
+import org.apache.gravitino.dto.policy.PolicyContentDTO;
+import org.apache.gravitino.json.JsonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestPolicyUpdatesRequest {
+
+  @Test
+  public void testRenamePolicyRequestSerDe() throws JsonProcessingException {
+    PolicyUpdateRequest.RenamePolicyRequest request =
+        new PolicyUpdateRequest.RenamePolicyRequest("policy_test_new");
+    String serJson = JsonUtils.objectMapper().writeValueAsString(request);
+    PolicyUpdateRequest.RenamePolicyRequest deserRequest =
+        JsonUtils.objectMapper().readValue(serJson, 
PolicyUpdateRequest.RenamePolicyRequest.class);
+    Assertions.assertEquals(request, deserRequest);
+    Assertions.assertEquals("policy_test_new", deserRequest.getNewName());
+  }
+
+  @Test
+  public void testUpdatePolicyCommentRequestSerDe() throws 
JsonProcessingException {
+    PolicyUpdateRequest.UpdatePolicyCommentRequest request =
+        new PolicyUpdateRequest.UpdatePolicyCommentRequest("policy comment 
new");
+    String serJson = JsonUtils.objectMapper().writeValueAsString(request);
+    PolicyUpdateRequest.UpdatePolicyCommentRequest deserRequest =
+        JsonUtils.objectMapper()
+            .readValue(serJson, 
PolicyUpdateRequest.UpdatePolicyCommentRequest.class);
+    Assertions.assertEquals(request, deserRequest);
+    Assertions.assertEquals("policy comment new", 
deserRequest.getNewComment());
+  }
+
+  @Test
+  public void testUpdatePolicyContentRequestSerDe() throws 
JsonProcessingException {
+    PolicyContentDTO contentDTO =
+        PolicyContentDTO.CustomContentDTO.builder()
+            .withCustomRules(ImmutableMap.of("rule1", "value1"))
+            .withProperties(ImmutableMap.of("key", "value"))
+            .build();
+    PolicyUpdateRequest.UpdatePolicyContentRequest request =
+        new PolicyUpdateRequest.UpdatePolicyContentRequest("test_type", 
contentDTO);
+    String serJson = JsonUtils.objectMapper().writeValueAsString(request);
+    PolicyUpdateRequest.UpdatePolicyContentRequest deserRequest =
+        JsonUtils.objectMapper()
+            .readValue(serJson, 
PolicyUpdateRequest.UpdatePolicyContentRequest.class);
+    Assertions.assertEquals(request, deserRequest);
+    Assertions.assertEquals("test_type", deserRequest.getPolicyType());
+    Assertions.assertEquals(contentDTO, deserRequest.getNewContent());
+  }
+
+  @Test
+  public void testPolicyUpdatesRequestSerDe() throws JsonProcessingException {
+    PolicyUpdateRequest request = new 
PolicyUpdateRequest.RenamePolicyRequest("policy_test_new");
+    PolicyUpdateRequest request1 =
+        new PolicyUpdateRequest.UpdatePolicyCommentRequest("policy comment 
new");
+    PolicyUpdateRequest request2 =
+        new PolicyUpdateRequest.UpdatePolicyContentRequest(
+            "test_type",
+            PolicyContentDTO.CustomContentDTO.builder()
+                .withCustomRules(ImmutableMap.of("rule1", "value1"))
+                .withProperties(ImmutableMap.of("key", "value"))
+                .build());
+
+    List<PolicyUpdateRequest> updates = ImmutableList.of(request, request1, 
request2);
+    PolicyUpdatesRequest policyUpdatesRequest = new 
PolicyUpdatesRequest(updates);
+    String serJson = 
JsonUtils.objectMapper().writeValueAsString(policyUpdatesRequest);
+    PolicyUpdatesRequest deserRequest =
+        JsonUtils.objectMapper().readValue(serJson, 
PolicyUpdatesRequest.class);
+    Assertions.assertEquals(policyUpdatesRequest, deserRequest);
+    Assertions.assertEquals(updates, deserRequest.getUpdates());
+  }
+}
diff --git a/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java 
b/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
index 2e7485c5fa..cf67a10aa9 100644
--- a/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
+++ b/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
@@ -491,6 +491,11 @@ public class PolicyManager implements PolicyDispatcher {
         newComment = ((PolicyChange.UpdatePolicyComment) 
change).getNewComment();
       } else if (change instanceof PolicyChange.UpdateContent) {
         PolicyChange.UpdateContent updateContent = 
(PolicyChange.UpdateContent) change;
+        Preconditions.checkArgument(
+            
policyEntity.policyType().equalsIgnoreCase(updateContent.getPolicyType()),
+            "Policy type mismatch: expected %s but got %s",
+            policyEntity.policyType(),
+            updateContent.getPolicyType());
         newContent = updateContent.getContent();
       } else {
         throw new IllegalArgumentException("Unsupported policy change: " + 
change);
diff --git 
a/core/src/test/java/org/apache/gravitino/policy/TestPolicyManager.java 
b/core/src/test/java/org/apache/gravitino/policy/TestPolicyManager.java
index 7cd95b88fa..dc92e25415 100644
--- a/core/src/test/java/org/apache/gravitino/policy/TestPolicyManager.java
+++ b/core/src/test/java/org/apache/gravitino/policy/TestPolicyManager.java
@@ -334,11 +334,20 @@ public class TestPolicyManager {
     // test update content
     Map<String, Object> newCustomRules = ImmutableMap.of("rule3", 1, "rule4", 
"value2");
     PolicyContent newContent = PolicyContents.custom(newCustomRules, null);
-    PolicyChange contentChange = PolicyChange.updateContent(newContent);
+    PolicyChange contentChange = PolicyChange.updateContent("test", 
newContent);
     Policy updatedContentPolicy =
         policyManager.alterPolicy(METALAKE, changedCommentPolicy.name(), 
contentChange);
     Assertions.assertEquals(newContent, updatedContentPolicy.content());
 
+    // test policy type mismatch
+    PolicyChange typeChange = PolicyChange.updateContent("mismatch_type", 
newContent);
+    Exception e =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () -> policyManager.alterPolicy(METALAKE, 
updatedContentPolicy.name(), typeChange));
+    Assertions.assertEquals(
+        "Policy type mismatch: expected test but got mismatch_type", 
e.getMessage());
+
     // test disable policy
     Assertions.assertDoesNotThrow(
         () -> policyManager.disablePolicy(METALAKE, renamedPolicy.name()));
diff --git 
a/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java 
b/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
index da76701444..3063a12278 100644
--- a/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
+++ b/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
@@ -41,6 +41,7 @@ import org.apache.gravitino.lineage.LineageService;
 import org.apache.gravitino.metalake.MetalakeDispatcher;
 import org.apache.gravitino.metrics.MetricsSystem;
 import org.apache.gravitino.metrics.source.MetricsSource;
+import org.apache.gravitino.policy.PolicyDispatcher;
 import org.apache.gravitino.server.authentication.ServerAuthenticator;
 import org.apache.gravitino.server.authorization.GravitinoAuthorizerProvider;
 import org.apache.gravitino.server.web.ConfigServlet;
@@ -139,6 +140,7 @@ public class GravitinoServer extends ResourceConfig {
             
bind(gravitinoEnv.filesetDispatcher()).to(FilesetDispatcher.class).ranked(1);
             
bind(gravitinoEnv.topicDispatcher()).to(TopicDispatcher.class).ranked(1);
             
bind(gravitinoEnv.tagDispatcher()).to(TagDispatcher.class).ranked(1);
+            
bind(gravitinoEnv.policyDispatcher()).to(PolicyDispatcher.class).ranked(1);
             bind(gravitinoEnv.credentialOperationDispatcher())
                 .to(CredentialOperationDispatcher.class)
                 .ranked(1);
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
index 4e6190a440..2e933c10c9 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
@@ -42,6 +42,8 @@ import 
org.apache.gravitino.exceptions.NonEmptySchemaException;
 import org.apache.gravitino.exceptions.NotFoundException;
 import org.apache.gravitino.exceptions.NotInUseException;
 import org.apache.gravitino.exceptions.PartitionAlreadyExistsException;
+import org.apache.gravitino.exceptions.PolicyAlreadyAssociatedException;
+import org.apache.gravitino.exceptions.PolicyAlreadyExistsException;
 import org.apache.gravitino.exceptions.RoleAlreadyExistsException;
 import org.apache.gravitino.exceptions.SchemaAlreadyExistsException;
 import org.apache.gravitino.exceptions.TableAlreadyExistsException;
@@ -124,6 +126,11 @@ public class ExceptionHandlers {
     return TagExceptionHandler.INSTANCE.handle(op, tag, parent, e);
   }
 
+  public static Response handlePolicyException(
+      OperationType op, String policy, String parent, Exception e) {
+    return PolicyExceptionHandler.INSTANCE.handle(op, policy, parent, e);
+  }
+
   public static Response handleCredentialException(
       OperationType op, String metadataObjectName, Exception e) {
     return CredentialExceptionHandler.INSTANCE.handle(op, metadataObjectName, 
"", e);
@@ -715,6 +722,44 @@ public class ExceptionHandlers {
     }
   }
 
+  private static class PolicyExceptionHandler extends BaseExceptionHandler {
+
+    private static final ExceptionHandler INSTANCE = new 
PolicyExceptionHandler();
+
+    private static String getPolicyErrorMsg(
+        String policy, String operation, String parent, String reason) {
+      return String.format(
+          "Failed to operate policy(s)%s operation [%s] under object [%s], 
reason [%s]",
+          policy, operation, parent, reason);
+    }
+
+    @Override
+    public Response handle(OperationType op, String policy, String parent, 
Exception e) {
+      String formatted = StringUtil.isBlank(policy) ? "" : " [" + policy + "]";
+      String errorMsg = getPolicyErrorMsg(formatted, op.name(), parent, 
getErrorMsg(e));
+      LOG.warn(errorMsg, e);
+
+      if (e instanceof IllegalArgumentException) {
+        return Utils.illegalArguments(errorMsg, e);
+
+      } else if (e instanceof NotFoundException) {
+        return Utils.notFound(errorMsg, e);
+
+      } else if (e instanceof PolicyAlreadyExistsException) {
+        return Utils.alreadyExists(errorMsg, e);
+
+      } else if (e instanceof PolicyAlreadyAssociatedException) {
+        return Utils.alreadyExists(errorMsg, e);
+
+      } else if (e instanceof NotInUseException) {
+        return Utils.notInUse(errorMsg, e);
+
+      } else {
+        return super.handle(op, policy, parent, e);
+      }
+    }
+  }
+
   private static class OwnerExceptionHandler extends BaseExceptionHandler {
 
     private static final ExceptionHandler INSTANCE = new 
OwnerExceptionHandler();
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/FilesetOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/FilesetOperations.java
index 68f8ef35ce..b0165b27e6 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/FilesetOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/FilesetOperations.java
@@ -335,7 +335,7 @@ public class FilesetOperations {
             NameIdentifier ident = NameIdentifierUtil.ofFileset(metalake, 
catalog, schema, fileset);
             boolean dropped = dispatcher.dropFileset(ident);
             if (!dropped) {
-              LOG.warn("Failed to drop fileset {} under schema {}", fileset, 
schema);
+              LOG.warn("Cannot find to be dropped fileset {} under schema {}", 
fileset, schema);
             }
 
             Response response = Utils.ok(new DropResponse(dropped));
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/PolicyOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/PolicyOperations.java
new file mode 100644
index 0000000000..93ec30eb32
--- /dev/null
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/PolicyOperations.java
@@ -0,0 +1,267 @@
+/*
+ * 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.server.web.rest;
+
+import static org.apache.gravitino.dto.util.DTOConverters.fromDTO;
+
+import com.codahale.metrics.annotation.ResponseMetered;
+import com.codahale.metrics.annotation.Timed;
+import java.util.Arrays;
+import java.util.Optional;
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.PATCH;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import org.apache.gravitino.dto.policy.PolicyDTO;
+import org.apache.gravitino.dto.requests.PolicyCreateRequest;
+import org.apache.gravitino.dto.requests.PolicySetRequest;
+import org.apache.gravitino.dto.requests.PolicyUpdateRequest;
+import org.apache.gravitino.dto.requests.PolicyUpdatesRequest;
+import org.apache.gravitino.dto.responses.BaseResponse;
+import org.apache.gravitino.dto.responses.DropResponse;
+import org.apache.gravitino.dto.responses.NameListResponse;
+import org.apache.gravitino.dto.responses.PolicyListResponse;
+import org.apache.gravitino.dto.responses.PolicyResponse;
+import org.apache.gravitino.dto.util.DTOConverters;
+import org.apache.gravitino.metrics.MetricNames;
+import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyChange;
+import org.apache.gravitino.policy.PolicyDispatcher;
+import org.apache.gravitino.server.web.Utils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Path("metalakes/{metalake}/policies")
+public class PolicyOperations {
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(PolicyOperations.class);
+
+  private final PolicyDispatcher policyDispatcher;
+
+  @Context private HttpServletRequest httpRequest;
+
+  @Inject
+  public PolicyOperations(PolicyDispatcher policyDispatcher) {
+    this.policyDispatcher = policyDispatcher;
+  }
+
+  @GET
+  @Produces("application/vnd.gravitino.v1+json")
+  @Timed(name = "list-policies." + MetricNames.HTTP_PROCESS_DURATION, absolute 
= true)
+  @ResponseMetered(name = "list-policies", absolute = true)
+  public Response listPolicies(
+      @PathParam("metalake") String metalake,
+      @QueryParam("details") @DefaultValue("false") boolean verbose) {
+    LOG.info(
+        "Received list policy {} request for metalake: {}", verbose ? "infos" 
: "names", metalake);
+
+    try {
+      return Utils.doAs(
+          httpRequest,
+          () -> {
+            if (verbose) {
+              Policy[] policies = policyDispatcher.listPolicyInfos(metalake);
+              PolicyDTO[] policyDTOs =
+                  Arrays.stream(policies)
+                      .map(p -> DTOConverters.toDTO(p, Optional.empty()))
+                      .toArray(PolicyDTO[]::new);
+
+              LOG.info("List {} policies info under metalake: {}", 
policyDTOs.length, metalake);
+              return Utils.ok(new PolicyListResponse(policyDTOs));
+
+            } else {
+              String[] policyNames = policyDispatcher.listPolicies(metalake);
+              policyNames = policyNames == null ? new String[0] : policyNames;
+
+              LOG.info("List {} policies under metalake: {}", 
policyNames.length, metalake);
+              return Utils.ok(new NameListResponse(policyNames));
+            }
+          });
+    } catch (Exception e) {
+      return ExceptionHandlers.handlePolicyException(OperationType.LIST, "", 
metalake, e);
+    }
+  }
+
+  @POST
+  @Produces("application/vnd.gravitino.v1+json")
+  @Timed(name = "create-policy." + MetricNames.HTTP_PROCESS_DURATION, absolute 
= true)
+  @ResponseMetered(name = "create-policy", absolute = true)
+  public Response createPolicy(
+      @PathParam("metalake") String metalake, PolicyCreateRequest request) {
+    LOG.info("Received create policy request under metalake: {}", metalake);
+
+    try {
+      return Utils.doAs(
+          httpRequest,
+          () -> {
+            request.validate();
+            Policy policy =
+                Policy.BuiltInType.fromPolicyType(request.getPolicyType())
+                        == Policy.BuiltInType.CUSTOM
+                    ? policyDispatcher.createPolicy(
+                        metalake,
+                        request.getName(),
+                        request.getPolicyType(),
+                        request.getComment(),
+                        request.getEnabled(),
+                        request.getExclusive(),
+                        request.getInheritable(),
+                        request.getSupportedObjectTypes(),
+                        fromDTO(request.getPolicyContent()))
+                    : policyDispatcher.createPolicy(
+                        metalake,
+                        request.getName(),
+                        request.getPolicyType(),
+                        request.getComment(),
+                        request.getEnabled(),
+                        fromDTO(request.getPolicyContent()));
+
+            LOG.info("Created policy: {} under metalake: {}", policy.name(), 
metalake);
+            return Utils.ok(new PolicyResponse(DTOConverters.toDTO(policy, 
Optional.empty())));
+          });
+    } catch (Exception e) {
+      return ExceptionHandlers.handlePolicyException(
+          OperationType.CREATE, request.getName(), metalake, e);
+    }
+  }
+
+  @GET
+  @Path("{policy}")
+  @Produces("application/vnd.gravitino.v1+json")
+  @Timed(name = "get-policy." + MetricNames.HTTP_PROCESS_DURATION, absolute = 
true)
+  @ResponseMetered(name = "get-policy", absolute = true)
+  public Response getPolicy(
+      @PathParam("metalake") String metalake, @PathParam("policy") String 
name) {
+    LOG.info("Received get policy request for policy: {} under metalake: {}", 
name, metalake);
+
+    try {
+      return Utils.doAs(
+          httpRequest,
+          () -> {
+            Policy policy = policyDispatcher.getPolicy(metalake, name);
+            LOG.info("Get policy: {} under metalake: {}", name, metalake);
+            return Utils.ok(new PolicyResponse(DTOConverters.toDTO(policy, 
Optional.empty())));
+          });
+    } catch (Exception e) {
+      return ExceptionHandlers.handlePolicyException(OperationType.GET, name, 
metalake, e);
+    }
+  }
+
+  @PUT
+  @Path("{policy}")
+  @Produces("application/vnd.gravitino.v1+json")
+  @Timed(name = "alter-policy." + MetricNames.HTTP_PROCESS_DURATION, absolute 
= true)
+  @ResponseMetered(name = "alter-policy", absolute = true)
+  public Response alterPolicy(
+      @PathParam("metalake") String metalake,
+      @PathParam("policy") String name,
+      PolicyUpdatesRequest request) {
+    LOG.info("Received alter policy request for policy: {} under metalake: 
{}", name, metalake);
+
+    try {
+      return Utils.doAs(
+          httpRequest,
+          () -> {
+            request.validate();
+
+            PolicyChange[] changes =
+                request.getUpdates().stream()
+                    .map(PolicyUpdateRequest::policyChange)
+                    .toArray(PolicyChange[]::new);
+            Policy policy = policyDispatcher.alterPolicy(metalake, name, 
changes);
+
+            LOG.info("Altered policy: {} under metalake: {}", name, metalake);
+            return Utils.ok(new PolicyResponse(DTOConverters.toDTO(policy, 
Optional.empty())));
+          });
+    } catch (Exception e) {
+      return ExceptionHandlers.handlePolicyException(OperationType.ALTER, 
name, metalake, e);
+    }
+  }
+
+  @PATCH
+  @Path("{policy}")
+  @Produces("application/vnd.gravitino.v1+json")
+  @Timed(name = "set-policy." + MetricNames.HTTP_PROCESS_DURATION, absolute = 
true)
+  @ResponseMetered(name = "set-policy", absolute = true)
+  public Response setPolicy(
+      @PathParam("metalake") String metalake,
+      @PathParam("policy") String name,
+      PolicySetRequest request) {
+    LOG.info("Received set policy request for policy: {} under metalake: {}", 
name, metalake);
+
+    try {
+      return Utils.doAs(
+          httpRequest,
+          () -> {
+            if (request.isEnable()) {
+              policyDispatcher.enablePolicy(metalake, name);
+            } else {
+              policyDispatcher.disablePolicy(metalake, name);
+            }
+
+            Response response = Utils.ok(new BaseResponse());
+            LOG.info(
+                "Successfully {} policy: {} under metalake: {}",
+                request.isEnable() ? "enabled" : "disabled",
+                name,
+                metalake);
+            return response;
+          });
+    } catch (Exception e) {
+      return ExceptionHandlers.handlePolicyException(OperationType.ENABLE, 
name, metalake, e);
+    }
+  }
+
+  @DELETE
+  @Path("{policy}")
+  @Produces("application/vnd.gravitino.v1+json")
+  @Timed(name = "delete-policy." + MetricNames.HTTP_PROCESS_DURATION, absolute 
= true)
+  @ResponseMetered(name = "delete-policy", absolute = true)
+  public Response deletePolicy(
+      @PathParam("metalake") String metalake, @PathParam("policy") String 
name) {
+    LOG.info("Received delete policy request for policy: {} under metalake: 
{}", name, metalake);
+
+    try {
+      return Utils.doAs(
+          httpRequest,
+          () -> {
+            boolean deleted = policyDispatcher.deletePolicy(metalake, name);
+            if (!deleted) {
+              LOG.warn("Cannot find to be deleted policy {} under metalake 
{}", name, metalake);
+            } else {
+              LOG.info("Deleted policy: {} under metalake: {}", name, 
metalake);
+            }
+
+            return Utils.ok(new DropResponse(deleted));
+          });
+    } catch (Exception e) {
+      return ExceptionHandlers.handlePolicyException(OperationType.DELETE, 
name, metalake, e);
+    }
+  }
+}
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/TableOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/TableOperations.java
index 3b20fe6d02..4302b737a9 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/TableOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/TableOperations.java
@@ -261,7 +261,7 @@ public class TableOperations {
             NameIdentifier ident = NameIdentifierUtil.ofTable(metalake, 
catalog, schema, table);
             boolean dropped = purge ? dispatcher.purgeTable(ident) : 
dispatcher.dropTable(ident);
             if (!dropped) {
-              LOG.warn("Failed to drop table {} under schema {}", table, 
schema);
+              LOG.warn("Cannot find to be dropped table {} under schema {}", 
table, schema);
             }
 
             Response response = Utils.ok(new DropResponse(dropped));
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/TagOperations.java 
b/server/src/main/java/org/apache/gravitino/server/web/rest/TagOperations.java
index 4166a83ebb..1b91b23cfd 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/TagOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/TagOperations.java
@@ -204,7 +204,7 @@ public class TagOperations {
           () -> {
             boolean deleted = tagDispatcher.deleteTag(metalake, name);
             if (!deleted) {
-              LOG.warn("Failed to delete tag {} under metalake {}", name, 
metalake);
+              LOG.warn("Cannot find to be deleted tag {} under metalake {}", 
name, metalake);
             } else {
               LOG.info("Deleted tag: {} under metalake: {}", name, metalake);
             }
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/TopicOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/TopicOperations.java
index 7712316ece..5e273643e9 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/TopicOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/TopicOperations.java
@@ -250,7 +250,7 @@ public class TopicOperations {
             boolean dropped = dispatcher.dropTopic(ident);
 
             if (!dropped) {
-              LOG.warn("Failed to drop topic {} under schema {}", topic, 
schema);
+              LOG.warn("Cannot find to be dropped topic {} under schema {}", 
topic, schema);
             }
 
             Response response = Utils.ok(new DropResponse(dropped));
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestPolicyOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestPolicyOperations.java
new file mode 100644
index 0000000000..917a86afc6
--- /dev/null
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestPolicyOperations.java
@@ -0,0 +1,597 @@
+/*
+ * 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.server.web.rest;
+
+import static org.apache.gravitino.dto.util.DTOConverters.toDTO;
+import static org.apache.gravitino.policy.Policy.SUPPORTS_ALL_OBJECT_TYPES;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import java.io.IOException;
+import java.time.Instant;
+import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.gravitino.dto.requests.PolicyCreateRequest;
+import org.apache.gravitino.dto.requests.PolicySetRequest;
+import org.apache.gravitino.dto.requests.PolicyUpdateRequest;
+import org.apache.gravitino.dto.requests.PolicyUpdatesRequest;
+import org.apache.gravitino.dto.responses.BaseResponse;
+import org.apache.gravitino.dto.responses.DropResponse;
+import org.apache.gravitino.dto.responses.ErrorConstants;
+import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.dto.responses.NameListResponse;
+import org.apache.gravitino.dto.responses.PolicyListResponse;
+import org.apache.gravitino.dto.responses.PolicyResponse;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.NoSuchPolicyException;
+import org.apache.gravitino.exceptions.PolicyAlreadyExistsException;
+import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.meta.PolicyEntity;
+import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyChange;
+import org.apache.gravitino.policy.PolicyContent;
+import org.apache.gravitino.policy.PolicyContents;
+import org.apache.gravitino.policy.PolicyDispatcher;
+import org.apache.gravitino.policy.PolicyManager;
+import org.apache.gravitino.rest.RESTUtils;
+import org.glassfish.jersey.client.HttpUrlConnectorProvider;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestPolicyOperations extends JerseyTest {
+
+  private static class MockServletRequestFactory extends 
ServletRequestFactoryBase {
+
+    @Override
+    public HttpServletRequest get() {
+      HttpServletRequest request = mock(HttpServletRequest.class);
+      when(request.getRemoteUser()).thenReturn(null);
+      return request;
+    }
+  }
+
+  private final PolicyManager policyManager = mock(PolicyManager.class);
+
+  private final String metalake = "test_metalake";
+
+  private final AuditInfo testAuditInfo1 =
+      
AuditInfo.builder().withCreator("user1").withCreateTime(Instant.now()).build();
+
+  @Override
+  protected Application configure() {
+    try {
+      forceSet(
+          TestProperties.CONTAINER_PORT, 
String.valueOf(RESTUtils.findAvailablePort(2000, 3000)));
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+
+    ResourceConfig resourceConfig = new ResourceConfig();
+    resourceConfig.register(PolicyOperations.class);
+    resourceConfig.register(
+        new AbstractBinder() {
+          @Override
+          protected void configure() {
+            bind(policyManager).to(PolicyDispatcher.class).ranked(2);
+            bindFactory(TestPolicyOperations.MockServletRequestFactory.class)
+                .to(HttpServletRequest.class);
+          }
+        });
+
+    return resourceConfig;
+  }
+
+  @Test
+  public void testListPolicies() {
+    String[] policies = new String[] {"policy1", "policy2"};
+    when(policyManager.listPolicies(metalake)).thenReturn(policies);
+
+    Response response =
+        target(policyPath(metalake))
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
response.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
response.getMediaType());
+
+    NameListResponse nameListResponse = 
response.readEntity(NameListResponse.class);
+    Assertions.assertEquals(0, nameListResponse.getCode());
+    Assertions.assertArrayEquals(policies, nameListResponse.getNames());
+
+    when(policyManager.listPolicies(metalake)).thenReturn(null);
+    Response resp1 =
+        target(policyPath(metalake))
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp1.getStatus());
+
+    NameListResponse nameListResponse1 = 
resp1.readEntity(NameListResponse.class);
+    Assertions.assertEquals(0, nameListResponse1.getCode());
+    Assertions.assertEquals(0, nameListResponse1.getNames().length);
+
+    when(policyManager.listPolicies(metalake)).thenReturn(new String[0]);
+    Response resp2 =
+        target(policyPath(metalake))
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp2.getStatus());
+
+    NameListResponse nameListResponse2 = 
resp2.readEntity(NameListResponse.class);
+    Assertions.assertEquals(0, nameListResponse2.getCode());
+    Assertions.assertEquals(0, nameListResponse2.getNames().length);
+
+    // Test throw NoSuchMetalakeException
+    doThrow(new NoSuchMetalakeException("mock 
error")).when(policyManager).listPolicies(metalake);
+    Response resp3 =
+        target(policyPath(metalake))
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+    Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(), 
resp3.getStatus());
+
+    ErrorResponse errorResp = resp3.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE, 
errorResp.getCode());
+    Assertions.assertEquals(NoSuchMetalakeException.class.getSimpleName(), 
errorResp.getType());
+
+    // Test throw RuntimeException
+    doThrow(new RuntimeException("mock 
error")).when(policyManager).listPolicies(metalake);
+    Response resp4 =
+        target(policyPath(metalake))
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+
+    Assertions.assertEquals(
+        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp4.getStatus());
+
+    ErrorResponse errorResp1 = resp4.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, 
errorResp1.getCode());
+    Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResp1.getType());
+  }
+
+  @Test
+  public void testListPolicyInfos() {
+    ImmutableMap<String, Object> contentFields = 
ImmutableMap.of("target_file_size_bytes", 1000);
+    PolicyContent content = PolicyContents.custom(contentFields, null);
+    PolicyEntity policy1 =
+        PolicyEntity.builder()
+            .withId(1L)
+            .withName("policy1")
+            .withPolicyType("my_compaction")
+            .withEnabled(false)
+            .withExclusive(true)
+            .withInheritable(true)
+            .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+            .withContent(content)
+            .withAuditInfo(testAuditInfo1)
+            .build();
+
+    PolicyEntity policy2 =
+        PolicyEntity.builder()
+            .withId(1L)
+            .withName("policy2")
+            .withPolicyType("my_compaction")
+            .withEnabled(false)
+            .withExclusive(true)
+            .withInheritable(true)
+            .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+            .withContent(content)
+            .withAuditInfo(testAuditInfo1)
+            .build();
+
+    Policy[] policies = new Policy[] {policy1, policy2};
+    when(policyManager.listPolicyInfos(metalake)).thenReturn(policies);
+
+    Response resp =
+        target(policyPath(metalake))
+            .queryParam("details", true)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    PolicyListResponse policyListResp = 
resp.readEntity(PolicyListResponse.class);
+    Assertions.assertEquals(0, policyListResp.getCode());
+    Assertions.assertEquals(policies.length, 
policyListResp.getPolicies().length);
+
+    Assertions.assertEquals(policy1.name(), 
policyListResp.getPolicies()[0].name());
+    Assertions.assertEquals(policy1.comment(), 
policyListResp.getPolicies()[0].comment());
+    Assertions.assertEquals(Optional.empty(), 
policyListResp.getPolicies()[0].inherited());
+
+    Assertions.assertEquals(policy2.name(), 
policyListResp.getPolicies()[1].name());
+    Assertions.assertEquals(policy2.comment(), 
policyListResp.getPolicies()[1].comment());
+    Assertions.assertEquals(Optional.empty(), 
policyListResp.getPolicies()[1].inherited());
+
+    // Test return empty array
+    when(policyManager.listPolicyInfos(metalake)).thenReturn(new Policy[0]);
+    Response resp2 =
+        target(policyPath(metalake))
+            .queryParam("details", true)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp2.getStatus());
+
+    PolicyListResponse policyListResp2 = 
resp2.readEntity(PolicyListResponse.class);
+    Assertions.assertEquals(0, policyListResp2.getCode());
+    Assertions.assertEquals(0, policyListResp2.getPolicies().length);
+  }
+
+  @Test
+  public void testCreatePolicy() {
+    ImmutableMap<String, Object> contentFields = 
ImmutableMap.of("target_file_size_bytes", 1000);
+    PolicyContent content = PolicyContents.custom(contentFields, null);
+    PolicyEntity policy1 =
+        PolicyEntity.builder()
+            .withId(1L)
+            .withName("policy1")
+            .withPolicyType("my_compaction")
+            .withEnabled(false)
+            .withExclusive(true)
+            .withInheritable(true)
+            .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+            .withContent(content)
+            .withAuditInfo(testAuditInfo1)
+            .build();
+    when(policyManager.createPolicy(
+            metalake,
+            "policy1",
+            "my_compaction",
+            null,
+            false,
+            true,
+            true,
+            SUPPORTS_ALL_OBJECT_TYPES,
+            content))
+        .thenReturn(policy1);
+
+    PolicyCreateRequest request =
+        new PolicyCreateRequest(
+            "policy1",
+            "my_compaction",
+            null,
+            false,
+            true,
+            true,
+            SUPPORTS_ALL_OBJECT_TYPES,
+            toDTO(content));
+    Response resp =
+        target(policyPath(metalake))
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    PolicyResponse policyResp = resp.readEntity(PolicyResponse.class);
+    Assertions.assertEquals(0, policyResp.getCode());
+
+    Policy respPolicy = policyResp.getPolicy();
+    Assertions.assertEquals(policy1.name(), respPolicy.name());
+    Assertions.assertEquals(policy1.comment(), respPolicy.comment());
+    Assertions.assertEquals(Optional.empty(), respPolicy.inherited());
+
+    // Test throw PolicyAlreadyExistsException
+    doThrow(new PolicyAlreadyExistsException("mock error"))
+        .when(policyManager)
+        .createPolicy(
+            any(), any(), any(), any(), anyBoolean(), anyBoolean(), 
anyBoolean(), any(), any());
+    Response resp1 =
+        target(policyPath(metalake))
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(), 
resp1.getStatus());
+
+    ErrorResponse errorResp = resp1.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.ALREADY_EXISTS_CODE, 
errorResp.getCode());
+    Assertions.assertEquals(
+        PolicyAlreadyExistsException.class.getSimpleName(), 
errorResp.getType());
+
+    // Test throw RuntimeException
+    doThrow(new RuntimeException("mock error"))
+        .when(policyManager)
+        .createPolicy(
+            any(), any(), any(), any(), anyBoolean(), anyBoolean(), 
anyBoolean(), any(), any());
+
+    Response resp2 =
+        target(policyPath(metalake))
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(
+        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp2.getStatus());
+
+    ErrorResponse errorResp1 = resp2.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, 
errorResp1.getCode());
+    Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResp1.getType());
+  }
+
+  @Test
+  public void testGetPolicy() {
+    ImmutableMap<String, Object> contentFields = 
ImmutableMap.of("target_file_size_bytes", 1000);
+    PolicyContent content = PolicyContents.custom(contentFields, null);
+    PolicyEntity policy1 =
+        PolicyEntity.builder()
+            .withId(1L)
+            .withName("policy1")
+            .withPolicyType("my_compaction")
+            .withEnabled(false)
+            .withExclusive(true)
+            .withInheritable(true)
+            .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+            .withContent(content)
+            .withAuditInfo(testAuditInfo1)
+            .build();
+    when(policyManager.getPolicy(metalake, "policy1")).thenReturn(policy1);
+
+    Response resp =
+        target(policyPath(metalake))
+            .path("policy1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    PolicyResponse policyResp = resp.readEntity(PolicyResponse.class);
+    Assertions.assertEquals(0, policyResp.getCode());
+
+    Policy respPolicy = policyResp.getPolicy();
+    Assertions.assertEquals(policy1.name(), respPolicy.name());
+    Assertions.assertEquals(policy1.comment(), respPolicy.comment());
+    Assertions.assertEquals(Optional.empty(), respPolicy.inherited());
+
+    // Test throw NoSuchPolicyException
+    doThrow(new NoSuchPolicyException("mock error"))
+        .when(policyManager)
+        .getPolicy(metalake, "policy1");
+
+    Response resp2 =
+        target(policyPath(metalake))
+            .path("policy1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+
+    Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(), 
resp2.getStatus());
+
+    ErrorResponse errorResp = resp2.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE, 
errorResp.getCode());
+    Assertions.assertEquals(NoSuchPolicyException.class.getSimpleName(), 
errorResp.getType());
+
+    // Test throw RuntimeException
+    doThrow(new RuntimeException("mock 
error")).when(policyManager).getPolicy(metalake, "policy1");
+
+    Response resp3 =
+        target(policyPath(metalake))
+            .path("policy1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+
+    Assertions.assertEquals(
+        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp3.getStatus());
+
+    ErrorResponse errorResp1 = resp3.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, 
errorResp1.getCode());
+    Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResp1.getType());
+  }
+
+  @Test
+  public void testAlterPolicy() {
+    ImmutableMap<String, Object> contentFields = 
ImmutableMap.of("target_file_size_bytes", 1000);
+    PolicyContent content = PolicyContents.custom(contentFields, null);
+    PolicyEntity newPolicy =
+        PolicyEntity.builder()
+            .withId(1L)
+            .withName("new_policy1")
+            .withPolicyType("my_compaction")
+            .withComment("new policy1 comment")
+            .withEnabled(false)
+            .withExclusive(true)
+            .withInheritable(true)
+            .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+            .withContent(content)
+            .withAuditInfo(testAuditInfo1)
+            .build();
+
+    PolicyChange[] changes =
+        new PolicyChange[] {
+          PolicyChange.rename("new_policy1"),
+          PolicyChange.updateComment("new policy1 comment"),
+          PolicyChange.updateContent("my_compaction", content)
+        };
+
+    when(policyManager.alterPolicy(metalake, "policy1", 
changes)).thenReturn(newPolicy);
+
+    PolicyUpdateRequest[] requests =
+        new PolicyUpdateRequest[] {
+          new PolicyUpdateRequest.RenamePolicyRequest("new_policy1"),
+          new PolicyUpdateRequest.UpdatePolicyCommentRequest("new policy1 
comment"),
+          new PolicyUpdateRequest.UpdatePolicyContentRequest("my_compaction", 
toDTO(content))
+        };
+    PolicyUpdatesRequest request = new 
PolicyUpdatesRequest(Lists.newArrayList(requests));
+    Response resp =
+        target(policyPath(metalake))
+            .path("policy1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    PolicyResponse policyResp = resp.readEntity(PolicyResponse.class);
+    Assertions.assertEquals(0, policyResp.getCode());
+
+    Policy respPolicy = policyResp.getPolicy();
+    Assertions.assertEquals(newPolicy.name(), respPolicy.name());
+    Assertions.assertEquals(newPolicy.comment(), respPolicy.comment());
+    Assertions.assertEquals(Optional.empty(), respPolicy.inherited());
+
+    // Test throw NoSuchPolicyException
+    doThrow(new NoSuchPolicyException("mock error"))
+        .when(policyManager)
+        .alterPolicy(any(), any(), any());
+
+    Response resp1 =
+        target(policyPath(metalake))
+            .path("policy1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(), 
resp1.getStatus());
+
+    ErrorResponse errorResp = resp1.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE, 
errorResp.getCode());
+    Assertions.assertEquals(NoSuchPolicyException.class.getSimpleName(), 
errorResp.getType());
+
+    // Test throw RuntimeException
+    doThrow(new RuntimeException("mock error"))
+        .when(policyManager)
+        .alterPolicy(any(), any(), any());
+
+    Response resp2 =
+        target(policyPath(metalake))
+            .path("policy1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(
+        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp2.getStatus());
+
+    ErrorResponse errorResp1 = resp2.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, 
errorResp1.getCode());
+    Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResp1.getType());
+  }
+
+  @Test
+  public void testSetPolicy() {
+    PolicySetRequest req = new PolicySetRequest(true);
+    doNothing().when(policyManager).enablePolicy(any(), any());
+
+    Response resp =
+        target(policyPath(metalake))
+            .path("policy1")
+            .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .method("PATCH", Entity.entity(req, 
MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+    BaseResponse baseResponse = resp.readEntity(BaseResponse.class);
+    Assertions.assertEquals(0, baseResponse.getCode());
+
+    req = new PolicySetRequest(false);
+    doNothing().when(policyManager).disablePolicy(any(), any());
+
+    Response resp1 =
+        target(policyPath(metalake))
+            .path("policy1")
+            .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .method("PATCH", Entity.entity(req, 
MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp1.getStatus());
+    BaseResponse baseResponse1 = resp1.readEntity(BaseResponse.class);
+    Assertions.assertEquals(0, baseResponse1.getCode());
+  }
+
+  @Test
+  public void testDeletePolicy() {
+    when(policyManager.deletePolicy(metalake, "policy1")).thenReturn(true);
+
+    Response resp =
+        target(policyPath(metalake))
+            .path("policy1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .delete();
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    DropResponse dropResp = resp.readEntity(DropResponse.class);
+    Assertions.assertEquals(0, dropResp.getCode());
+    Assertions.assertTrue(dropResp.dropped());
+
+    when(policyManager.deletePolicy(metalake, "policy1")).thenReturn(false);
+    Response resp1 =
+        target(policyPath(metalake))
+            .path("policy1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .delete();
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp1.getStatus());
+
+    DropResponse dropResp1 = resp1.readEntity(DropResponse.class);
+    Assertions.assertEquals(0, dropResp1.getCode());
+    Assertions.assertFalse(dropResp1.dropped());
+
+    // Test throw RuntimeException
+    doThrow(new RuntimeException("mock 
error")).when(policyManager).deletePolicy(any(), any());
+
+    Response resp2 =
+        target(policyPath(metalake))
+            .path("policy1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .delete();
+
+    Assertions.assertEquals(
+        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp2.getStatus());
+
+    ErrorResponse errorResp1 = resp2.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, 
errorResp1.getCode());
+    Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResp1.getType());
+  }
+
+  private String policyPath(String metalake) {
+    return "/metalakes/" + metalake + "/policies";
+  }
+}

Reply via email to