diff --git a/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreSessionImpl.java b/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreSessionImpl.java index 524d4701d..31cec98df 100644 --- a/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreSessionImpl.java +++ b/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreSessionImpl.java @@ -52,6 +52,7 @@ import org.apache.polaris.core.persistence.BaseMetaStoreManager; import org.apache.polaris.core.persistence.PrincipalSecretsGenerator; import org.apache.polaris.core.persistence.RetryOnConcurrencyException; +import org.apache.polaris.core.policy.PolarisPolicyMappingRecord; import org.apache.polaris.core.persistence.transactional.AbstractTransactionalPersistence; import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo; import org.apache.polaris.core.storage.PolarisStorageIntegration; @@ -60,6 +61,7 @@ import org.apache.polaris.jpa.models.ModelEntityActive; import org.apache.polaris.jpa.models.ModelEntityChangeTracking; import org.apache.polaris.jpa.models.ModelGrantRecord; +import org.apache.polaris.jpa.models.ModelPolicyMappingRecord; import org.apache.polaris.jpa.models.ModelPrincipalSecrets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -529,6 +531,90 @@ public int lookupEntityGrantRecordsVersion( .toList(); } + /** {@inheritDoc} */ + @Override + public void writeToPolicyMappingRecords( + @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord record) { + + this.store.writeToPolicyMappingRecords(localSession.get(), record); + } + + /** {@inheritDoc} */ + @Override + public void deleteFromPolicyMappingRecords( + @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord record) { + this.store.deleteFromPolicyMappingRecords(localSession.get(), record); + } + + /** {@inheritDoc} */ + @Override + public void deleteAllEntityPolicyMappingRecords( + @Nonnull PolarisCallContext callCtx, + @Nonnull PolarisEntityCore entity, + @Nonnull List mappingOnTarget, + @Nonnull List mappingOnPolicy) { + this.store.deleteAllEntityPolicyMappingRecords(localSession.get(), entity); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public PolarisPolicyMappingRecord lookupPolicyMappingRecord( + @Nonnull PolarisCallContext callCtx, + long targetCatalogId, + long targetId, + int policyTypeCode, + long policyCatalogId, + long policyId) { + return ModelPolicyMappingRecord.toPolicyMappingRecord( + this.store.lookupPolicyMappingRecord( + localSession.get(), + targetCatalogId, + targetId, + policyTypeCode, + policyCatalogId, + policyId)); + } + + /** {@inheritDoc} */ + @Nonnull + @Override + public List loadPoliciesOnTargetByType( + @Nonnull PolarisCallContext callCtx, + long targetCatalogId, + long targetId, + int policyTypeCode) { + return this.store + .loadPoliciesOnTargetByType(localSession.get(), targetCatalogId, targetId, policyTypeCode) + .stream() + .map(ModelPolicyMappingRecord::toPolicyMappingRecord) + .toList(); + } + + /** {@inheritDoc} */ + @Nonnull + @Override + public List loadAllPoliciesOnTarget( + @Nonnull PolarisCallContext callCtx, long targetCatalogId, long targetId) { + return this.store + .loadAllPoliciesOnTarget(localSession.get(), targetCatalogId, targetId) + .stream() + .map(ModelPolicyMappingRecord::toPolicyMappingRecord) + .toList(); + } + + /** {@inheritDoc} */ + @Nonnull + @Override + public List loadAllPoliciesOnPolicy( + @Nonnull PolarisCallContext callCtx, long policyCatalogId, long policyId) { + return this.store + .loadAllPoliciesOnPolicy(localSession.get(), policyCatalogId, policyId) + .stream() + .map(ModelPolicyMappingRecord::toPolicyMappingRecord) + .toList(); + } + /** {@inheritDoc} */ @Override public @Nullable PolarisPrincipalSecrets loadPrincipalSecrets( diff --git a/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkStore.java b/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkStore.java index bfc83ae37..ab1d8c91f 100644 --- a/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkStore.java +++ b/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkStore.java @@ -35,10 +35,12 @@ import org.apache.polaris.core.entity.PolarisEntityType; import org.apache.polaris.core.entity.PolarisGrantRecord; import org.apache.polaris.core.entity.PolarisPrincipalSecrets; +import org.apache.polaris.core.policy.PolarisPolicyMappingRecord; import org.apache.polaris.jpa.models.ModelEntity; import org.apache.polaris.jpa.models.ModelEntityActive; import org.apache.polaris.jpa.models.ModelEntityChangeTracking; import org.apache.polaris.jpa.models.ModelGrantRecord; +import org.apache.polaris.jpa.models.ModelPolicyMappingRecord; import org.apache.polaris.jpa.models.ModelPrincipalSecrets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -411,6 +413,121 @@ void deletePrincipalSecrets(EntityManager session, String clientId) { session.remove(modelPrincipalSecrets); } + void writeToPolicyMappingRecords( + EntityManager session, PolarisPolicyMappingRecord mappingRecord) { + diagnosticServices.check(session != null, "session_is_null"); + checkInitialized(); + + session.persist(ModelPolicyMappingRecord.fromPolicyMappingRecord(mappingRecord)); + } + + void deleteFromPolicyMappingRecords( + EntityManager session, PolarisPolicyMappingRecord mappingRecord) { + diagnosticServices.check(session != null, "session_is_null"); + checkInitialized(); + + ModelPolicyMappingRecord lookupPolicyMappingRecord = + lookupPolicyMappingRecord( + session, + mappingRecord.getTargetCatalogId(), + mappingRecord.getTargetId(), + mappingRecord.getPolicyTypeCode(), + mappingRecord.getPolicyCatalogId(), + mappingRecord.getPolicyId()); + + diagnosticServices.check(lookupPolicyMappingRecord != null, "policy_mapping_record_not_found"); + session.remove(lookupPolicyMappingRecord); + } + + void deleteAllEntityPolicyMappingRecords(EntityManager session, PolarisEntityCore entity) { + diagnosticServices.check(session != null, "session_is_null"); + checkInitialized(); + + loadAllPoliciesOnPolicy(session, entity.getCatalogId(), entity.getId()) + .forEach(session::remove); + loadAllPoliciesOnTarget(session, entity.getCatalogId(), entity.getId()) + .forEach(session::remove); + } + + ModelPolicyMappingRecord lookupPolicyMappingRecord( + EntityManager session, + long targetCatalogId, + long targetId, + long policyTypeCode, + long policyCatalogId, + long policyId) { + diagnosticServices.check(session != null, "session_is_null"); + checkInitialized(); + + return session + .createQuery( + "SELECT m from ModelPolicyMappingRecord m " + + "where m.targetCatalogId=:targetCatalogId " + + "and m.targetId=:targetId " + + "and m.policyTypeCode=:policyTypeCode " + + "and m.policyCatalogId=:policyCatalogId " + + "and m.policyId=:policyId", + ModelPolicyMappingRecord.class) + .setParameter("targetCatalogId", targetCatalogId) + .setParameter("targetId", targetId) + .setParameter("policyTypeCode", policyTypeCode) + .setParameter("policyCatalogId", policyCatalogId) + .setParameter("policyId", policyId) + .getResultStream() + .findFirst() + .orElse(null); + } + + List loadPoliciesOnTargetByType( + EntityManager session, long targetCatalogId, long targetId, int policyTypeCode) { + diagnosticServices.check(session != null, "session_is_null"); + checkInitialized(); + + return session + .createQuery( + "SELECT m from ModelPolicyMappingRecord m " + + "where m.targetCatalogId=:targetCatalogId " + + "and m.targetId=:targetId " + + "and m.policyTypeCode=:policyTypeCode", + ModelPolicyMappingRecord.class) + .setParameter("targetCatalogId", targetCatalogId) + .setParameter("targetId", targetId) + .setParameter("policyTypeCode", policyTypeCode) + .getResultList(); + } + + List loadAllPoliciesOnTarget( + EntityManager session, long targetCatalogId, long targetId) { + diagnosticServices.check(session != null, "session_is_null"); + checkInitialized(); + + return session + .createQuery( + "SELECT m from ModelPolicyMappingRecord m " + + " where m.targetCatalogId=:targetCatalogId " + + "and m.targetId=:targetId", + ModelPolicyMappingRecord.class) + .setParameter("targetCatalogId", targetCatalogId) + .setParameter("targetId", targetId) + .getResultList(); + } + + List loadAllPoliciesOnPolicy( + EntityManager session, long policyCatalogId, long policyId) { + diagnosticServices.check(session != null, "session_is_null"); + checkInitialized(); + + return session + .createQuery( + "SELECT m from ModelPolicyMappingRecord m " + + "where m.policyCatalogId=:policyCatalogId " + + "and m.policyId=:policyId", + ModelPolicyMappingRecord.class) + .setParameter("policyCatalogId", policyCatalogId) + .setParameter("policyId", policyId) + .getResultList(); + } + private void checkInitialized() { diagnosticServices.check(this.initialized.get(), "store_not_initialized"); } diff --git a/extension/persistence/jpa-model/src/main/java/org/apache/polaris/jpa/models/ModelPolicyMappingRecord.java b/extension/persistence/jpa-model/src/main/java/org/apache/polaris/jpa/models/ModelPolicyMappingRecord.java new file mode 100644 index 000000000..122eeadb8 --- /dev/null +++ b/extension/persistence/jpa-model/src/main/java/org/apache/polaris/jpa/models/ModelPolicyMappingRecord.java @@ -0,0 +1,153 @@ +/* + * 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.jpa.models; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.Table; +import jakarta.persistence.Version; +import org.apache.polaris.core.policy.PolarisPolicyMappingRecord; + +@Entity +@Table( + name = "POLICY_MAPPING_RECORDS", + indexes = { + @Index( + name = "POLICY_MAPPING_RECORDS_BY_POLICY_INDEX", + columnList = "policyCatalogId,policyId,targetCatalogId,targetId") + }) +public class ModelPolicyMappingRecord { + // id of the catalog where target entity resides + @Id private long targetCatalogId; + + // id of the target entity + @Id private long targetId; + + // id associated to the policy type + @Id private int policyTypeCode; + + // id of the catalog where the policy entity resides + @Id private long policyCatalogId; + + // id of the policy + @Id private long policyId; + + // additional parameters of the mapping + private String parameters; + + // Used for Optimistic Locking to handle concurrent reads and updates + @Version private long version; + + public long getTargetCatalogId() { + return targetCatalogId; + } + + public long getTargetId() { + return targetId; + } + + public int getPolicyTypeCode() { + return policyTypeCode; + } + + public long getPolicyCatalogId() { + return policyCatalogId; + } + + public long getPolicyId() { + return policyId; + } + + public String getParameters() { + return parameters; + } + + public static ModelPolicyMappingRecord.Builder builder() { + return new ModelPolicyMappingRecord.Builder(); + } + + public static final class Builder { + private final ModelPolicyMappingRecord policyMappingRecord; + + private Builder() { + policyMappingRecord = new ModelPolicyMappingRecord(); + } + + public Builder targetCatalogId(long targetCatalogId) { + policyMappingRecord.targetCatalogId = targetCatalogId; + return this; + } + + public Builder targetId(long targetId) { + policyMappingRecord.targetId = targetId; + return this; + } + + public Builder policyTypeCode(int policyTypeCode) { + policyMappingRecord.policyTypeCode = policyTypeCode; + return this; + } + + public Builder policyCatalogId(long policyCatalogId) { + policyMappingRecord.policyCatalogId = policyCatalogId; + return this; + } + + public Builder policyId(long policyId) { + policyMappingRecord.policyId = policyId; + return this; + } + + public Builder parameters(String parameters) { + policyMappingRecord.parameters = parameters; + return this; + } + + public ModelPolicyMappingRecord build() { + return policyMappingRecord; + } + } + + public static ModelPolicyMappingRecord fromPolicyMappingRecord( + PolarisPolicyMappingRecord record) { + if (record == null) return null; + + return ModelPolicyMappingRecord.builder() + .targetCatalogId(record.getTargetCatalogId()) + .targetId(record.getTargetId()) + .policyTypeCode(record.getPolicyTypeCode()) + .policyCatalogId(record.getPolicyCatalogId()) + .policyId(record.getPolicyId()) + .parameters(record.getParameters()) + .build(); + } + + public static PolarisPolicyMappingRecord toPolicyMappingRecord(ModelPolicyMappingRecord model) { + if (model == null) return null; + + return new PolarisPolicyMappingRecord( + model.getTargetCatalogId(), + model.getTargetId(), + model.getPolicyCatalogId(), + model.getPolicyId(), + model.getPolicyTypeCode(), + model.getParameters()); + } +} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisPolicyMappingRecord.java b/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisPolicyMappingRecord.java deleted file mode 100644 index 3d8c3b8b2..000000000 --- a/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisPolicyMappingRecord.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * 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.entity; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -public class PolarisPolicyMappingRecord { - // to serialize/deserialize properties - public static final String EMPTY_MAP_STRING = "{}"; - private static final ObjectMapper MAPPER = new ObjectMapper(); - - // id of the catalog where target entity resides - private long targetCatalogId; - - // id of the target entity - private long targetId; - - // id of the catalog where the policy entity resides - private long policyCatalogId; - - // id of the policy - private long policyId; - - // id associated to the policy type - private int policyTypeCode; - - // additional parameters of the mapping - private String parameters; - - public PolarisPolicyMappingRecord() {} - - public long getTargetCatalogId() { - return targetCatalogId; - } - - public void setTargetCatalogId(long targetCatalogId) { - this.targetCatalogId = targetCatalogId; - } - - public long getTargetId() { - return targetId; - } - - public void setTargetId(long targetId) { - this.targetId = targetId; - } - - public long getPolicyId() { - return policyId; - } - - public void setPolicyId(long policyId) { - this.policyId = policyId; - } - - public int getPolicyTypeCode() { - return policyTypeCode; - } - - public void setPolicyTypeCode(int policyTypeCode) { - this.policyTypeCode = policyTypeCode; - } - - public long getPolicyCatalogId() { - return policyCatalogId; - } - - public void setPolicyCatalogId(long policyCatalogId) { - this.policyCatalogId = policyCatalogId; - } - - public String getParameters() { - return parameters; - } - - public void setParameters(String parameters) { - this.parameters = parameters; - } - - public Map getParametersAsMap() { - if (parameters == null) { - return new HashMap<>(); - } - try { - return MAPPER.readValue(parameters, new TypeReference<>() {}); - } catch (JsonProcessingException ex) { - throw new IllegalStateException( - String.format("Failed to deserialize json. parameters %s", parameters), ex); - } - } - - public void setParametersAsMap(Map parameters) { - try { - this.parameters = - parameters == null ? EMPTY_MAP_STRING : MAPPER.writeValueAsString(parameters); - } catch (JsonProcessingException ex) { - throw new IllegalStateException( - String.format("Failed to serialize json. properties %s", parameters), ex); - } - } - - /** - * Constructor - * - * @param targetCatalogId id of the catalog where target entity resides - * @param targetId id of the target entity - * @param policyCatalogId id of the catalog where the policy entity resides - * @param policyId id of the policy - * @param policyTypeCode id associated to the policy type - * @param parameters additional parameters of the mapping - */ - @JsonCreator - public PolarisPolicyMappingRecord( - @JsonProperty("targetCatalogId") long targetCatalogId, - @JsonProperty("targetId") long targetId, - @JsonProperty("policyCatalogId") long policyCatalogId, - @JsonProperty("policyId") long policyId, - @JsonProperty("policyTypeCode") int policyTypeCode, - @JsonProperty("parameters") String parameters) { - this.targetCatalogId = targetCatalogId; - this.targetId = targetId; - this.policyCatalogId = policyCatalogId; - this.policyId = policyId; - this.policyTypeCode = policyTypeCode; - this.parameters = parameters; - } - - public PolarisPolicyMappingRecord( - long targetCatalogId, - long targetId, - long policyCatalogId, - long policyId, - int policyTypeCode, - Map parameters) { - this.targetCatalogId = targetCatalogId; - this.targetId = targetId; - this.policyCatalogId = policyCatalogId; - this.policyId = policyId; - this.policyTypeCode = policyTypeCode; - this.setParametersAsMap(parameters); - } - - /** - * Copy constructor - * - * @param policyMappingRecord policy mapping rec to copy - */ - public PolarisPolicyMappingRecord(PolarisPolicyMappingRecord policyMappingRecord) { - this.targetCatalogId = policyMappingRecord.getTargetCatalogId(); - this.targetId = policyMappingRecord.getTargetId(); - this.policyCatalogId = policyMappingRecord.getPolicyCatalogId(); - this.policyId = policyMappingRecord.getPolicyId(); - this.policyTypeCode = policyMappingRecord.getPolicyTypeCode(); - this.parameters = policyMappingRecord.getParameters(); - } - - @Override - public String toString() { - return "PolarisPolicyMappingRec{" - + "targetCatalogId=" - + targetCatalogId - + ", targetId=" - + targetId - + ", policyCatalogId=" - + policyCatalogId - + ", policyId=" - + policyId - + ", policyType='" - + policyTypeCode - + ", parameters='" - + parameters - + "}"; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PolarisPolicyMappingRecord that = (PolarisPolicyMappingRecord) o; - return targetCatalogId == that.targetCatalogId - && targetId == that.targetId - && policyCatalogId == that.policyCatalogId - && policyId == that.policyId - && policyTypeCode == that.policyTypeCode - && Objects.equals(parameters, that.parameters); - } - - @Override - public int hashCode() { - return java.util.Objects.hash(targetId, policyId, policyCatalogId, policyTypeCode, parameters); - } -} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/BasePersistence.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/BasePersistence.java index b92a25af9..acf7b59a1 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/BasePersistence.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/BasePersistence.java @@ -31,6 +31,7 @@ import org.apache.polaris.core.entity.PolarisEntityId; import org.apache.polaris.core.entity.PolarisEntityType; import org.apache.polaris.core.entity.PolarisGrantRecord; +import org.apache.polaris.core.policy.PolarisPolicyMappingRecord; /** * Interface to the Polaris persistence backend, with which to persist and retrieve all the data @@ -362,4 +363,101 @@ boolean hasChildren( @Nullable PolarisEntityType optionalEntityType, long catalogId, long parentId); + + /** + * Write the specified policyMappingRecord to the policy_mapping_records table. If there is a + * conflict (existing record with the same PK), all attributes of the new record will replace the + * existing one. + * + * @param callCtx call context + * @param record policy mapping record to write, potentially replacing an existing policy mapping + * with the same key + */ + void writeToPolicyMappingRecords( + @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord record); + + /** + * Delete the specified policyMappingRecord to the policy_mapping_records table. + * + * @param callCtx call context + * @param record policy mapping record to delete. + */ + void deleteFromPolicyMappingRecords( + @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord record); + + /** + * Delete the all policy mapping records in the policy_mapping_records table for the specified + * entity. This method will delete all policy mapping records on the entity + * + * @param callCtx call context + * @param entity entity whose policy mapping records should be deleted + * @param mappingOnTarget all mappings on that target entity. Empty list if that entity is not a + * target + * @param mappingOnPolicy all mappings on that policy entity. Empty list if that entity is not a + * policy + */ + void deleteAllEntityPolicyMappingRecords( + @Nonnull PolarisCallContext callCtx, + @Nonnull PolarisEntityCore entity, + @Nonnull List mappingOnTarget, + @Nonnull List mappingOnPolicy); + + /** + * Look up the specified policy mapping record from the policy_mapping_records table. Return NULL + * if not found + * + * @param callCtx call context + * @param targetCatalogId catalog id of the target entity, NULL_ID if the entity is top-level + * @param targetId id of the target entity + * @param policyTypeCode type code of the policy entity + * @param policyCatalogId catalog id of the policy entity + * @param policyId id of the policy entity + * @return the policy mapping record if found, NULL if not found + */ + @Nullable + PolarisPolicyMappingRecord lookupPolicyMappingRecord( + @Nonnull PolarisCallContext callCtx, + long targetCatalogId, + long targetId, + int policyTypeCode, + long policyCatalogId, + long policyId); + + /** + * Get all policies on the specified target entity with specified policy type. + * + * @param callCtx call context + * @param targetCatalogId catalog id of the target entity, NULL_ID if the entity is top-level + * @param targetId id of the target entity + * @param policyTypeCode type code of the policy entity + * @return the list of policy mapping records for the specified target entity with the specified + * policy type + */ + @Nonnull + List loadPoliciesOnTargetByType( + @Nonnull PolarisCallContext callCtx, long targetCatalogId, long targetId, int policyTypeCode); + + /** + * Get all policies on the specified target entity. + * + * @param callCtx call context + * @param targetCatalogId catalog id of the target entity, NULL_ID if the entity is top-level + * @param targetId id of the target entity + * @return the list of policy mapping records for the specified target entity + */ + @Nonnull + List loadAllPoliciesOnTarget( + @Nonnull PolarisCallContext callCtx, long targetCatalogId, long targetId); + + /** + * Get all targets of the specified policy entity + * + * @param callCtx call context + * @param policyCatalogId catalog id of the policy entity, NULL_ID if the entity is top-level + * @param policyId id of the policy entity + * @return the list of policy mapping records for the specified policy entity + */ + @Nonnull + List loadAllPoliciesOnPolicy( + @Nonnull PolarisCallContext callCtx, long policyCatalogId, long policyId); } diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/PolarisTreeMapMetaStoreSessionImpl.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/PolarisTreeMapMetaStoreSessionImpl.java index dbd2ce834..5170a0b51 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/PolarisTreeMapMetaStoreSessionImpl.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/PolarisTreeMapMetaStoreSessionImpl.java @@ -38,9 +38,11 @@ import org.apache.polaris.core.entity.PolarisPrincipalSecrets; import org.apache.polaris.core.persistence.BaseMetaStoreManager; import org.apache.polaris.core.persistence.PrincipalSecretsGenerator; +import org.apache.polaris.core.policy.PolarisPolicyMappingRecord; import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo; import org.apache.polaris.core.storage.PolarisStorageIntegration; import org.apache.polaris.core.storage.PolarisStorageIntegrationProvider; +import org.jetbrains.annotations.NotNull; public class PolarisTreeMapMetaStoreSessionImpl extends AbstractTransactionalPersistence { @@ -413,6 +415,88 @@ public int lookupEntityGrantRecordsVersion( .readRange(this.store.buildPrefixKeyComposite(granteeCatalogId, granteeId)); } + /** {@inheritDoc} */ + @Override + public void writeToPolicyMappingRecords( + @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord record) { + this.store.getSlicePolicyMappingRecords().write(record); + this.store.getSlicePolicyMappingRecordsByPolicy().write(record); + } + + /** {@inheritDoc} */ + @Override + public void deleteFromPolicyMappingRecords( + @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord record) { + this.store.getSlicePolicyMappingRecords().delete(record); + this.store.getSlicePolicyMappingRecordsByPolicy().delete(record); + } + + /** {@inheritDoc} */ + @Override + public void deleteAllEntityPolicyMappingRecords( + @Nonnull PolarisCallContext callCtx, + @Nonnull PolarisEntityCore entity, + @Nonnull List mappingOnTarget, + @Nonnull List mappingOnPolicy) { + // build composite prefix key and delete policy mapping records on the indexed side of each + // mapping table + String prefix = this.store.buildPrefixKeyComposite(entity.getCatalogId(), entity.getId()); + this.store.getSlicePolicyMappingRecords().deleteRange(prefix); + this.store.getSlicePolicyMappingRecordsByPolicy().deleteRange(prefix); + + // also delete the other side. We need to delete these mapping one at a time versus doing a + // range delete + mappingOnTarget.forEach(record -> this.store.getSlicePolicyMappingRecords().delete(record)); + mappingOnPolicy.forEach( + record -> this.store.getSlicePolicyMappingRecordsByPolicy().delete(record)); + } + + /** {@inheritDoc} */ + @Override + public @Nullable PolarisPolicyMappingRecord lookupPolicyMappingRecord( + @Nonnull PolarisCallContext callCtx, + long targetCatalogId, + long targetId, + int policyTypeCode, + long policyCatalogId, + long policyId) { + return this.store + .getSlicePolicyMappingRecords() + .read( + this.store.buildKeyComposite( + targetCatalogId, targetId, policyTypeCode, policyCatalogId, policyId)); + } + + /** {@inheritDoc} */ + @Override + public @NotNull List loadPoliciesOnTargetByType( + @Nonnull PolarisCallContext callCtx, + long targetCatalogId, + long targetId, + int policyTypeCode) { + return this.store + .getSlicePolicyMappingRecords() + .readRange(this.store.buildPrefixKeyComposite(targetCatalogId, targetId, policyTypeCode)); + } + + /** {@inheritDoc} */ + @Override + public @Nonnull List loadAllPoliciesOnTarget( + @Nonnull PolarisCallContext callCtx, long targetCatalogId, long targetId) { + return this.store + .getSlicePolicyMappingRecords() + .readRange(this.store.buildPrefixKeyComposite(targetCatalogId, targetId)); + } + + /** {@inheritDoc} */ + @Override + public @Nonnull List loadAllPoliciesOnPolicy( + @Nonnull PolarisCallContext callCtx, long policyCatalogId, long policyId) { + return this.store + .getSlicePolicyMappingRecordsByPolicy() + .readRange(this.store.buildPrefixKeyComposite(policyCatalogId, policyId)); + } + /** {@inheritDoc} */ @Override public @Nullable PolarisPrincipalSecrets loadPrincipalSecrets( diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/PolarisTreeMapStore.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/PolarisTreeMapStore.java index fe80bdf23..72f14f474 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/PolarisTreeMapStore.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/PolarisTreeMapStore.java @@ -31,6 +31,7 @@ import org.apache.polaris.core.entity.PolarisEntityCore; import org.apache.polaris.core.entity.PolarisGrantRecord; import org.apache.polaris.core.entity.PolarisPrincipalSecrets; +import org.apache.polaris.core.policy.PolarisPolicyMappingRecord; /** Implements a simple in-memory store for Polaris, using tree-map */ public class PolarisTreeMapStore { @@ -209,6 +210,10 @@ public boolean isWrite() { // slice to store principal secrets private final Slice slicePrincipalSecrets; + private final Slice slicePolicyMappingRecords; + + private final Slice slicePolicyMappingRecordsByPolicy; + // next id generator private final AtomicLong nextId = new AtomicLong(); @@ -266,6 +271,29 @@ public PolarisTreeMapStore(@Nonnull PolarisDiagnostics diagnostics) { principalSecrets -> String.format("%s", principalSecrets.getPrincipalClientId()), PolarisPrincipalSecrets::new); + this.slicePolicyMappingRecords = + new Slice<>( + policyMappingRecord -> + String.format( + "%d::%d::%d::%d::%d", + policyMappingRecord.getTargetCatalogId(), + policyMappingRecord.getTargetId(), + policyMappingRecord.getPolicyTypeCode(), + policyMappingRecord.getPolicyCatalogId(), + policyMappingRecord.getPolicyId()), + PolarisPolicyMappingRecord::new); + + this.slicePolicyMappingRecordsByPolicy = + new Slice<>( + policyMappingRecord -> + String.format( + "%d::%d::%d::%d", + policyMappingRecord.getPolicyCatalogId(), + policyMappingRecord.getPolicyId(), + policyMappingRecord.getTargetCatalogId(), + policyMappingRecord.getTargetId()), + PolarisPolicyMappingRecord::new); + // no transaction open yet this.diagnosticServices = diagnostics; this.tr = null; @@ -345,6 +373,8 @@ private void startWriteTransaction() { this.sliceGrantRecords.startWriteTransaction(); this.sliceGrantRecordsByGrantee.startWriteTransaction(); this.slicePrincipalSecrets.startWriteTransaction(); + this.slicePolicyMappingRecords.startWriteTransaction(); + this.slicePolicyMappingRecordsByPolicy.startWriteTransaction(); } /** Rollback transaction */ @@ -355,6 +385,8 @@ void rollback() { this.sliceGrantRecords.rollback(); this.sliceGrantRecordsByGrantee.rollback(); this.slicePrincipalSecrets.rollback(); + this.slicePolicyMappingRecords.rollback(); + this.slicePolicyMappingRecordsByPolicy.rollback(); } /** Ensure that a read/write FDB transaction has been started */ @@ -493,6 +525,14 @@ public Slice getSlicePrincipalSecrets() { return slicePrincipalSecrets; } + public Slice getSlicePolicyMappingRecords() { + return slicePolicyMappingRecords; + } + + public Slice getSlicePolicyMappingRecordsByPolicy() { + return slicePolicyMappingRecordsByPolicy; + } + /** * Next sequence number generator * @@ -511,5 +551,7 @@ void deleteAll() { this.sliceGrantRecordsByGrantee.deleteAll(); this.sliceGrantRecords.deleteAll(); this.slicePrincipalSecrets.deleteAll(); + this.slicePolicyMappingRecords.deleteAll(); + this.slicePolicyMappingRecordsByPolicy.deleteAll(); } } diff --git a/polaris-core/src/main/java/org/apache/polaris/core/policy/PolarisPolicyMappingRecord.java b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolarisPolicyMappingRecord.java new file mode 100644 index 000000000..d4bf118b6 --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolarisPolicyMappingRecord.java @@ -0,0 +1,215 @@ +/* + * 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.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class PolarisPolicyMappingRecord { + // to serialize/deserialize properties + public static final String EMPTY_MAP_STRING = "{}"; + private static final ObjectMapper MAPPER = new ObjectMapper(); + + // id of the catalog where target entity resides + private long targetCatalogId; + + // id of the target entity + private long targetId; + + // id of the catalog where the policy entity resides + private long policyCatalogId; + + // id of the policy + private long policyId; + + // id associated to the policy type + private int policyTypeCode; + + // additional parameters of the mapping + private String parameters; + + public PolarisPolicyMappingRecord() {} + + public long getTargetCatalogId() { + return targetCatalogId; + } + + public void setTargetCatalogId(long targetCatalogId) { + this.targetCatalogId = targetCatalogId; + } + + public long getTargetId() { + return targetId; + } + + public void setTargetId(long targetId) { + this.targetId = targetId; + } + + public long getPolicyId() { + return policyId; + } + + public void setPolicyId(long policyId) { + this.policyId = policyId; + } + + public int getPolicyTypeCode() { + return policyTypeCode; + } + + public void setPolicyTypeCode(int policyTypeCode) { + this.policyTypeCode = policyTypeCode; + } + + public long getPolicyCatalogId() { + return policyCatalogId; + } + + public void setPolicyCatalogId(long policyCatalogId) { + this.policyCatalogId = policyCatalogId; + } + + public String getParameters() { + return parameters; + } + + public void setParameters(String parameters) { + this.parameters = parameters; + } + + public Map getParametersAsMap() { + if (parameters == null) { + return new HashMap<>(); + } + try { + return MAPPER.readValue(parameters, new TypeReference<>() {}); + } catch (JsonProcessingException ex) { + throw new IllegalStateException( + String.format("Failed to deserialize json. parameters %s", parameters), ex); + } + } + + public void setParametersAsMap(Map parameters) { + try { + this.parameters = + parameters == null ? EMPTY_MAP_STRING : MAPPER.writeValueAsString(parameters); + } catch (JsonProcessingException ex) { + throw new IllegalStateException( + String.format("Failed to serialize json. properties %s", parameters), ex); + } + } + + /** + * Constructor + * + * @param targetCatalogId id of the catalog where target entity resides + * @param targetId id of the target entity + * @param policyCatalogId id of the catalog where the policy entity resides + * @param policyId id of the policy + * @param policyTypeCode id associated to the policy type + * @param parameters additional parameters of the mapping + */ + @JsonCreator + public PolarisPolicyMappingRecord( + @JsonProperty("targetCatalogId") long targetCatalogId, + @JsonProperty("targetId") long targetId, + @JsonProperty("policyCatalogId") long policyCatalogId, + @JsonProperty("policyId") long policyId, + @JsonProperty("policyTypeCode") int policyTypeCode, + @JsonProperty("parameters") String parameters) { + this.targetCatalogId = targetCatalogId; + this.targetId = targetId; + this.policyCatalogId = policyCatalogId; + this.policyId = policyId; + this.policyTypeCode = policyTypeCode; + this.parameters = parameters; + } + + public PolarisPolicyMappingRecord( + long targetCatalogId, + long targetId, + long policyCatalogId, + long policyId, + int policyTypeCode, + Map parameters) { + this.targetCatalogId = targetCatalogId; + this.targetId = targetId; + this.policyCatalogId = policyCatalogId; + this.policyId = policyId; + this.policyTypeCode = policyTypeCode; + this.setParametersAsMap(parameters); + } + + /** + * Copy constructor + * + * @param policyMappingRecord policy mapping rec to copy + */ + public PolarisPolicyMappingRecord(PolarisPolicyMappingRecord policyMappingRecord) { + this.targetCatalogId = policyMappingRecord.getTargetCatalogId(); + this.targetId = policyMappingRecord.getTargetId(); + this.policyCatalogId = policyMappingRecord.getPolicyCatalogId(); + this.policyId = policyMappingRecord.getPolicyId(); + this.policyTypeCode = policyMappingRecord.getPolicyTypeCode(); + this.parameters = policyMappingRecord.getParameters(); + } + + @Override + public String toString() { + return "PolarisPolicyMappingRec{" + + "targetCatalogId=" + + targetCatalogId + + ", targetId=" + + targetId + + ", policyCatalogId=" + + policyCatalogId + + ", policyId=" + + policyId + + ", policyType='" + + policyTypeCode + + ", parameters='" + + parameters + + "}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PolarisPolicyMappingRecord that = (PolarisPolicyMappingRecord) o; + return targetCatalogId == that.targetCatalogId + && targetId == that.targetId + && policyCatalogId == that.policyCatalogId + && policyId == that.policyId + && policyTypeCode == that.policyTypeCode + && Objects.equals(parameters, that.parameters); + } + + @Override + public int hashCode() { + return java.util.Objects.hash(targetId, policyId, policyCatalogId, policyTypeCode, parameters); + } +} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/entity/PolicyEntity.java b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java similarity index 93% rename from polaris-core/src/main/java/org/apache/polaris/core/entity/PolicyEntity.java rename to polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java index f2952693b..c66b79bef 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/entity/PolicyEntity.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java @@ -16,13 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.core.entity; +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.policy.PolicyType; +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 { diff --git a/polaris-core/src/test/java/org/apache/polaris/core/entity/PolicyEntityTest.java b/polaris-core/src/test/java/org/apache/polaris/core/entity/PolicyEntityTest.java index 9e017d486..a347b2929 100644 --- a/polaris-core/src/test/java/org/apache/polaris/core/entity/PolicyEntityTest.java +++ b/polaris-core/src/test/java/org/apache/polaris/core/entity/PolicyEntityTest.java @@ -19,6 +19,7 @@ package org.apache.polaris.core.entity; import org.apache.iceberg.catalog.Namespace; +import org.apache.polaris.core.policy.PolicyEntity; import org.apache.polaris.core.policy.PredefinedPolicyType; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test;