From a1c7ce36edc455c1096193af244892b837c6c083 Mon Sep 17 00:00:00 2001 From: Honah J Date: Thu, 6 Mar 2025 19:23:15 -0800 Subject: [PATCH] PolicyEntity and PolicyType --- .../core/entity/PolarisEntityType.java | 3 +- .../polaris/core/policy/PolicyEntity.java | 128 ++++++++++++++++++ .../polaris/core/policy/PolicyType.java | 84 ++++++++++++ .../core/policy/PredefinedPolicyType.java | 108 +++++++++++++++ .../polaris/core/policy/PolicyEntityTest.java | 53 ++++++++ .../polaris/core/policy/PolicyTypeTest.java | 56 ++++++++ 6 files changed, 431 insertions(+), 1 deletion(-) create mode 100644 polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java create mode 100644 polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyType.java create mode 100644 polaris-core/src/main/java/org/apache/polaris/core/policy/PredefinedPolicyType.java create mode 100644 polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java create mode 100644 polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyTypeTest.java diff --git a/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisEntityType.java b/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisEntityType.java index af50eed6f..4a3eada34 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisEntityType.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisEntityType.java @@ -34,7 +34,8 @@ public enum PolarisEntityType { // generic table is either a view or a real table TABLE_LIKE(7, NAMESPACE, false, false), TASK(8, ROOT, false, false), - FILE(9, TABLE_LIKE, false, false); + FILE(9, TABLE_LIKE, false, false), + POLICY(10, NAMESPACE, false, false); // to efficiently map a code to its corresponding entity type, use a reverse array which // is initialized below diff --git a/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java new file mode 100644 index 000000000..c66b79bef --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java @@ -0,0 +1,128 @@ +/* + * 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.polaris.core.policy; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.google.common.base.Preconditions; +import org.apache.iceberg.catalog.Namespace; +import org.apache.iceberg.rest.RESTUtil; +import org.apache.polaris.core.entity.NamespaceEntity; +import org.apache.polaris.core.entity.PolarisBaseEntity; +import org.apache.polaris.core.entity.PolarisEntity; +import org.apache.polaris.core.entity.PolarisEntityType; + +public class PolicyEntity extends PolarisEntity { + + public static final String POLICY_TYPE_CODE_KEY = "policy-type-code"; + public static final String POLICY_DESCRIPTION_KEY = "policy-description"; + public static final String POLICY_VERSION_KEY = "policy-version"; + public static final String POLICY_CONTENT_KEY = "policy-content"; + + PolicyEntity(PolarisBaseEntity sourceEntity) { + super(sourceEntity); + } + + public static PolicyEntity of(PolarisBaseEntity sourceEntity) { + if (sourceEntity != null) { + return new PolicyEntity(sourceEntity); + } + + return null; + } + + @JsonIgnore + public PolicyType getPolicyType() { + return PolicyType.fromCode(getPolicyTypeCode()); + } + + @JsonIgnore + public int getPolicyTypeCode() { + String policyTypeCode = getPropertiesAsMap().get(POLICY_TYPE_CODE_KEY); + if (policyTypeCode != null) { + return Integer.parseInt(policyTypeCode); + } + + return -1; + } + + @JsonIgnore + public String getDescription() { + return getPropertiesAsMap().get(POLICY_DESCRIPTION_KEY); + } + + @JsonIgnore + public String getContent() { + return getPropertiesAsMap().get(POLICY_CONTENT_KEY); + } + + @JsonIgnore + public String getPolicyVersion() { + return getPropertiesAsMap().get(POLICY_VERSION_KEY); + } + + public static class Builder extends PolarisEntity.BaseBuilder { + public Builder(Namespace namespace, String policyName) { + super(); + setType(PolarisEntityType.POLICY); + setParentNamespace(namespace); + setName(policyName); + setPolicyVersion(0); + } + + public Builder(PolicyEntity original) { + super(original); + } + + @Override + public PolicyEntity build() { + Preconditions.checkArgument( + properties.get(POLICY_TYPE_CODE_KEY) != null, "Policy type must be specified"); + + return new PolicyEntity(buildBase()); + } + + public Builder setParentNamespace(Namespace namespace) { + if (namespace != null && !namespace.isEmpty()) { + internalProperties.put( + NamespaceEntity.PARENT_NAMESPACE_KEY, RESTUtil.encodeNamespace(namespace)); + } + return this; + } + + public Builder setPolicyType(PolicyType policyType) { + properties.put(POLICY_TYPE_CODE_KEY, Integer.toString(policyType.getCode())); + return this; + } + + public Builder setDescription(String description) { + properties.put(POLICY_DESCRIPTION_KEY, description); + return this; + } + + public Builder setPolicyVersion(long version) { + properties.put(POLICY_VERSION_KEY, Long.toString(version)); + return this; + } + + public Builder setContent(String content) { + properties.put(POLICY_CONTENT_KEY, content); + return this; + } + } +} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyType.java b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyType.java new file mode 100644 index 000000000..b280ffe8f --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyType.java @@ -0,0 +1,84 @@ +/* + * 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.polaris.core.policy; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import jakarta.annotation.Nullable; + +/** + * Represents a policy type in Polaris. A policy type defines a category of policies that may be + * either predefined or custom (user-defined). + * + *

A policy type can be either inheritable or non-inheritable. Inheritable policies are passed + * down to lower-level entities (e.g., from a namespace to a table). + */ +public interface PolicyType { + + /** + * Retrieves the unique type code associated with this policy type. + * + * @return the type code of the policy type + */ + @JsonValue + int getCode(); + + /** + * Retrieves the human-readable name of this policy type. + * + * @return the name of the policy type + */ + String getName(); + + /** + * Determines whether this policy type is inheritable. + * + * @return {@code true} if the policy type is inheritable, otherwise {@code false} + */ + boolean isInheritable(); + + /** + * Retrieves a {@link PolicyType} instance corresponding to the given type code. + * + *

This method searches for the policy type in predefined policy types. If a custom policy type + * storage mechanism is implemented in the future, it may also check registered custom policy + * types. + * + * @param code the type code of the policy type + * @return the corresponding {@link PolicyType}, or {@code null} if no matching type is found + */ + @JsonCreator + static @Nullable PolicyType fromCode(int code) { + return PredefinedPolicyType.fromCode(code); + } + + /** + * Retrieves a {@link PolicyType} instance corresponding to the given policy name. + * + *

This method searches for the policy type in predefined policy types. If a custom policy type + * storage mechanism is implemented in the future, it may also check registered custom policy + * types. + * + * @param name the name of the policy type + * @return the corresponding {@link PolicyType}, or {@code null} if no matching type is found + */ + static @Nullable PolicyType fromName(String name) { + return PredefinedPolicyType.fromName(name); + } +} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/policy/PredefinedPolicyType.java b/polaris-core/src/main/java/org/apache/polaris/core/policy/PredefinedPolicyType.java new file mode 100644 index 000000000..c43bc82f8 --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/policy/PredefinedPolicyType.java @@ -0,0 +1,108 @@ +/* + * 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.polaris.core.policy; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.collect.ImmutableMap; +import jakarta.annotation.Nullable; + +/* Represents all predefined policy types in Polaris */ +public enum PredefinedPolicyType implements PolicyType { + DATA_COMPACTION(0, "system.data-compaction", true), + METADATA_COMPACTION(1, "system.metadata-compaction", true), + ORPHAN_FILE_REMOVAL(2, "system.orphan-file-removal", true), + SNAPSHOT_RETENTION(3, "system.snapshot-retention", true); + + private final int code; + private final String name; + private final boolean isInheritable; + private static final PredefinedPolicyType[] REVERSE_CODE_MAPPING_ARRAY; + private static final ImmutableMap REVERSE_NAME_MAPPING_ARRAY; + + static { + int maxId = 0; + for (PredefinedPolicyType policyType : PredefinedPolicyType.values()) { + if (maxId < policyType.code) { + maxId = policyType.code; + } + } + + REVERSE_CODE_MAPPING_ARRAY = new PredefinedPolicyType[maxId + 1]; + ImmutableMap.Builder builder = ImmutableMap.builder(); + // populate both + for (PredefinedPolicyType policyType : PredefinedPolicyType.values()) { + REVERSE_CODE_MAPPING_ARRAY[policyType.code] = policyType; + builder.put(policyType.name, policyType); + } + REVERSE_NAME_MAPPING_ARRAY = builder.build(); + } + + PredefinedPolicyType(int code, String name, boolean isInheritable) { + this.code = code; + this.name = name; + this.isInheritable = isInheritable; + } + + /** {@inheritDoc} */ + @Override + @JsonValue + public int getCode() { + return code; + } + + /** {@inheritDoc} */ + @Override + public String getName() { + return name; + } + + /** {@inheritDoc} */ + @Override + public boolean isInheritable() { + return isInheritable; + } + + /** + * Retrieves a {@link PredefinedPolicyType} instance corresponding to the given type code. + * + * @param code the type code of the predefined policy type + * @return the corresponding {@link PredefinedPolicyType}, or {@code null} if no matching type is + * found + */ + @JsonCreator + public static @Nullable PredefinedPolicyType fromCode(int code) { + if (code >= REVERSE_CODE_MAPPING_ARRAY.length) { + return null; + } + + return REVERSE_CODE_MAPPING_ARRAY[code]; + } + + /** + * Retrieves a {@link PredefinedPolicyType} instance corresponding to the given policy name. + * + * @param name the name of the predefined policy type + * @return the corresponding {@link PredefinedPolicyType}, or {@code null} if no matching type is + * found + */ + public static @Nullable PredefinedPolicyType fromName(String name) { + return REVERSE_NAME_MAPPING_ARRAY.get(name); + } +} diff --git a/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java b/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java new file mode 100644 index 000000000..c9ff8f0e1 --- /dev/null +++ b/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java @@ -0,0 +1,53 @@ +/* + * 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.polaris.core.policy; + +import java.util.stream.Stream; +import org.apache.iceberg.catalog.Namespace; +import org.apache.polaris.core.entity.PolarisEntityType; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class PolicyEntityTest { + + static Stream policyTypes() { + return Stream.of( + Arguments.of(PredefinedPolicyType.DATA_COMPACTION), + Arguments.of(PredefinedPolicyType.METADATA_COMPACTION), + Arguments.of(PredefinedPolicyType.ORPHAN_FILE_REMOVAL), + Arguments.of(PredefinedPolicyType.METADATA_COMPACTION)); + } + + @ParameterizedTest + @MethodSource("policyTypes") + public void testPolicyEntity(PolicyType policyType) { + PolicyEntity entity = + new PolicyEntity.Builder(Namespace.of("NS1"), "testPolicy") + .setPolicyType(policyType) + .setContent("test_content") + .setPolicyVersion(0) + .build(); + Assertions.assertThat(entity.getType()).isEqualTo(PolarisEntityType.POLICY); + Assertions.assertThat(entity.getPolicyType()).isEqualTo(policyType); + Assertions.assertThat(entity.getPolicyTypeCode()).isEqualTo(policyType.getCode()); + Assertions.assertThat(entity.getContent()).isEqualTo("test_content"); + } +} diff --git a/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyTypeTest.java b/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyTypeTest.java new file mode 100644 index 000000000..a690cfd06 --- /dev/null +++ b/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyTypeTest.java @@ -0,0 +1,56 @@ +/* + * 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.polaris.core.policy; + +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class PolicyTypeTest { + + static Stream predefinedPolicyTypes() { + return Stream.of( + Arguments.of(0, "system.data-compaction", true), + Arguments.of(1, "system.metadata-compaction", true), + Arguments.of(2, "system.orphan-file-removal", true), + Arguments.of(3, "system.snapshot-retention", true)); + } + + @ParameterizedTest + @MethodSource("predefinedPolicyTypes") + public void testPredefinedPolicyTypeFromCode(int code, String name, boolean isInheritable) { + PolicyType policyType = PolicyType.fromCode(code); + Assertions.assertThat(policyType).isNotNull(); + Assertions.assertThat(policyType.getCode()).isEqualTo(code); + Assertions.assertThat(policyType.getName()).isEqualTo(name); + Assertions.assertThat(policyType.isInheritable()).isEqualTo(isInheritable); + } + + @ParameterizedTest + @MethodSource("predefinedPolicyTypes") + public void testPredefinedPolicyTypeFromName(int code, String name, boolean isInheritable) { + PolicyType policyType = PolicyType.fromName(name); + Assertions.assertThat(policyType).isNotNull(); + Assertions.assertThat(policyType.getCode()).isEqualTo(code); + Assertions.assertThat(policyType.getName()).isEqualTo(name); + Assertions.assertThat(policyType.isInheritable()).isEqualTo(isInheritable); + } +}