Skip to content

Commit e619a00

Browse files
authored
Core: Add data compaction policy content parser and validator (#1238)
1 parent 74b8621 commit e619a00

File tree

12 files changed

+681
-0
lines changed

12 files changed

+681
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.core.policy;
20+
21+
/** A marker interface for policy content */
22+
public interface PolicyContent {}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.core.policy.validator;
20+
21+
import org.apache.polaris.core.exceptions.PolarisException;
22+
23+
/** Exception thrown when a policy is invalid or violates defined rules. */
24+
public class InvalidPolicyException extends PolarisException {
25+
public InvalidPolicyException(String message) {
26+
super(message);
27+
}
28+
29+
public InvalidPolicyException(String message, Throwable cause) {
30+
super(message, cause);
31+
}
32+
33+
public InvalidPolicyException(Throwable cause) {
34+
super("Invalid policy", cause);
35+
}
36+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.core.policy.validator;
20+
21+
import org.apache.polaris.core.entity.PolarisEntitySubType;
22+
import org.apache.polaris.core.entity.PolarisEntityType;
23+
24+
/** Validates and parses a given policy content string against its defined schema. */
25+
public interface PolicyValidator {
26+
27+
/**
28+
* Validates the provided policy content.
29+
*
30+
* @param content the policy content to parse and validate
31+
* @throws InvalidPolicyException if the content is not valid
32+
*/
33+
void validate(String content) throws InvalidPolicyException;
34+
35+
/**
36+
* Determines whether the policy is attachable to a target entity.
37+
*
38+
* <p>This method examines the provided {@link PolarisEntityType} and {@link PolarisEntitySubType}
39+
* to decide if a policy is applicable for attachment to the target entity.
40+
*
41+
* @param entityType the type of the target entity
42+
* @param entitySubType the subtype of the target entity
43+
* @return {@code true} if the policy can be attached to the target entity; {@code false}
44+
* otherwise
45+
*/
46+
boolean canAttach(PolarisEntityType entityType, PolarisEntitySubType entitySubType);
47+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.core.policy.validator;
20+
21+
import com.fasterxml.jackson.databind.DeserializationFeature;
22+
import com.fasterxml.jackson.databind.ObjectMapper;
23+
24+
public class PolicyValidatorUtil {
25+
public static final ObjectMapper MAPPER = configureMapper();
26+
27+
private static ObjectMapper configureMapper() {
28+
ObjectMapper mapper = new ObjectMapper();
29+
// Fails if a required field (in the constructor) is missing
30+
mapper.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, true);
31+
// Fails if a required field is present but explicitly null, e.g., {"enable": null}
32+
mapper.configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, true);
33+
return mapper;
34+
}
35+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.core.policy.validator;
20+
21+
import com.google.common.base.Preconditions;
22+
import org.apache.polaris.core.entity.PolarisEntity;
23+
import org.apache.polaris.core.policy.PolicyEntity;
24+
import org.apache.polaris.core.policy.PredefinedPolicyTypes;
25+
import org.apache.polaris.core.policy.validator.datacompaction.DataCompactionPolicyValidator;
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
28+
29+
/**
30+
* Validates a given {@link PolicyEntity} against its defined policy type.
31+
*
32+
* <p>This class maps the policy type code from the {@code PolicyEntity} to a predefined policy
33+
* type, then delegates parsing/validation to a specific validator implementation.
34+
*/
35+
public class PolicyValidators {
36+
private static final Logger LOGGER = LoggerFactory.getLogger(PolicyValidators.class);
37+
38+
/**
39+
* Validates the given policy.
40+
*
41+
* @param policy the policy entity to validate
42+
* @throws InvalidPolicyException if the policy type is unknown or unsupported, or if the policy
43+
* content is invalid
44+
*/
45+
public static void validate(PolicyEntity policy) {
46+
Preconditions.checkNotNull(policy, "Policy must not be null");
47+
48+
var type = PredefinedPolicyTypes.fromCode(policy.getPolicyTypeCode());
49+
Preconditions.checkArgument(type != null, "Unknown policy type: " + policy.getPolicyTypeCode());
50+
51+
switch (type) {
52+
case DATA_COMPACTION:
53+
DataCompactionPolicyValidator.INSTANCE.validate(policy.getContent());
54+
break;
55+
56+
// To support additional policy types in the future, add cases here.
57+
case METADATA_COMPACTION:
58+
case SNAPSHOT_RETENTION:
59+
case ORPHAN_FILE_REMOVAL:
60+
default:
61+
throw new InvalidPolicyException("Unsupported policy type: " + type.getName());
62+
}
63+
64+
LOGGER.info("Policy validated successfully: {}", type.getName());
65+
}
66+
67+
/**
68+
* Determines whether the given policy can be attached to the specified target entity.
69+
*
70+
* @param policy the policy entity to check
71+
* @param targetEntity the target Polaris entity to attach the policy to
72+
* @return {@code true} if the policy is attachable to the target entity; {@code false} otherwise
73+
*/
74+
public static boolean canAttach(PolicyEntity policy, PolarisEntity targetEntity) {
75+
Preconditions.checkNotNull(policy, "Policy must not be null");
76+
Preconditions.checkNotNull(targetEntity, "Target entity must not be null");
77+
78+
var policyType = PredefinedPolicyTypes.fromCode(policy.getPolicyTypeCode());
79+
Preconditions.checkArgument(
80+
policyType != null, "Unknown policy type: " + policy.getPolicyTypeCode());
81+
82+
switch (policyType) {
83+
case DATA_COMPACTION:
84+
return DataCompactionPolicyValidator.INSTANCE.canAttach(
85+
targetEntity.getType(), targetEntity.getSubType());
86+
// To support additional policy types in the future, add cases here.
87+
case METADATA_COMPACTION:
88+
case SNAPSHOT_RETENTION:
89+
case ORPHAN_FILE_REMOVAL:
90+
default:
91+
LOGGER.warn("Attachment not supported for policy type: {}", policyType.getName());
92+
return false;
93+
}
94+
}
95+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.core.policy.validator;
20+
21+
import com.fasterxml.jackson.core.JsonParser;
22+
import com.fasterxml.jackson.databind.DeserializationContext;
23+
import com.fasterxml.jackson.databind.JsonDeserializer;
24+
import java.io.IOException;
25+
26+
public class StrictBooleanDeserializer extends JsonDeserializer<Boolean> {
27+
@Override
28+
public Boolean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
29+
String text = p.getText();
30+
if ("true".equals(text)) {
31+
return Boolean.TRUE;
32+
} else if ("false".equals(text)) {
33+
return Boolean.FALSE;
34+
} else {
35+
throw new InvalidPolicyException("Invalid boolean value: " + text);
36+
}
37+
}
38+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.core.policy.validator.datacompaction;
20+
21+
import com.fasterxml.jackson.annotation.JsonCreator;
22+
import com.fasterxml.jackson.annotation.JsonProperty;
23+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
24+
import com.google.common.base.Strings;
25+
import java.util.Map;
26+
import java.util.Set;
27+
import org.apache.polaris.core.policy.PolicyContent;
28+
import org.apache.polaris.core.policy.validator.InvalidPolicyException;
29+
import org.apache.polaris.core.policy.validator.PolicyValidatorUtil;
30+
import org.apache.polaris.core.policy.validator.StrictBooleanDeserializer;
31+
32+
public class DataCompactionPolicyContent implements PolicyContent {
33+
private static final String DEFAULT_POLICY_SCHEMA_VERSION = "2025-02-03";
34+
private static final Set<String> POLICY_SCHEMA_VERSIONS = Set.of(DEFAULT_POLICY_SCHEMA_VERSION);
35+
36+
@JsonDeserialize(using = StrictBooleanDeserializer.class)
37+
private Boolean enable;
38+
39+
private String version;
40+
private Map<String, String> config;
41+
42+
@JsonCreator
43+
public DataCompactionPolicyContent(
44+
@JsonProperty(value = "enable", required = true) boolean enable) {
45+
this.enable = enable;
46+
}
47+
48+
public String getVersion() {
49+
return version;
50+
}
51+
52+
public void setVersion(String version) {
53+
this.version = version;
54+
}
55+
56+
public Boolean enabled() {
57+
return enable;
58+
}
59+
60+
public void setEnabled(Boolean enable) {
61+
this.enable = enable;
62+
}
63+
64+
public Map<String, String> getConfig() {
65+
return config;
66+
}
67+
68+
public void setConfig(Map<String, String> config) {
69+
this.config = config;
70+
}
71+
72+
public static DataCompactionPolicyContent fromString(String content) {
73+
if (Strings.isNullOrEmpty(content)) {
74+
throw new InvalidPolicyException("Policy is empty");
75+
}
76+
77+
try {
78+
DataCompactionPolicyContent policy =
79+
PolicyValidatorUtil.MAPPER.readValue(content, DataCompactionPolicyContent.class);
80+
if (policy == null) {
81+
throw new InvalidPolicyException("Invalid policy");
82+
}
83+
84+
if (Strings.isNullOrEmpty(policy.getVersion())) {
85+
policy.setVersion(DEFAULT_POLICY_SCHEMA_VERSION);
86+
}
87+
88+
if (!POLICY_SCHEMA_VERSIONS.contains(policy.getVersion())) {
89+
throw new InvalidPolicyException("Invalid policy version: " + policy.getVersion());
90+
}
91+
92+
return policy;
93+
} catch (Exception e) {
94+
throw new InvalidPolicyException(e);
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)