Skip to content

Commit b5bc2b5

Browse files
committed
Introduce MutationPoint-based filtering for EntityMutator
1 parent d2636da commit b5bc2b5

File tree

11 files changed

+171
-13
lines changed

11 files changed

+171
-13
lines changed

polaris-core/src/main/java/org/apache/polaris/core/identity/mutation/EntityMutationEngine.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ public interface EntityMutationEngine {
3333
/**
3434
* Applies all registered entity mutators to the provided entity, in order.
3535
*
36+
* @param mutationPoint The point in the entity lifecycle where mutations should be applied.
3637
* @param entity The original Polaris entity to mutate.
3738
* @return A new or modified instance of {@link PolarisBaseEntity} after all mutations are
3839
* applied.
3940
*/
40-
PolarisBaseEntity applyMutations(PolarisBaseEntity entity);
41+
PolarisBaseEntity applyMutations(MutationPoint mutationPoint, PolarisBaseEntity entity);
4142
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
20+
package org.apache.polaris.core.identity.mutation;
21+
22+
/**
23+
* Defines points in the entity lifecycle where {@link EntityMutator} can be applied.
24+
*
25+
* <p>Each mutation point corresponds to a specific hook where mutations may be executed. Mutators
26+
* can declare which points they support, allowing the engine to invoke only the relevant ones.
27+
*/
28+
public enum MutationPoint {
29+
30+
/** Applied before a catalog entity is persisted. */
31+
CATALOG_PRE_PERSIST(0),
32+
;
33+
34+
private final int id;
35+
36+
MutationPoint(int id) {
37+
this.id = id;
38+
}
39+
40+
public int id() {
41+
return id;
42+
}
43+
}

polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TransactionalMetaStoreManagerImpl.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
4848
import org.apache.polaris.core.entity.PolarisPrivilege;
4949
import org.apache.polaris.core.entity.PolarisTaskConstants;
50+
import org.apache.polaris.core.identity.mutation.EntityMutationEngine;
51+
import org.apache.polaris.core.identity.mutation.MutationPoint;
5052
import org.apache.polaris.core.persistence.*;
5153
import org.apache.polaris.core.persistence.dao.entity.BaseResult;
5254
import org.apache.polaris.core.persistence.dao.entity.ChangeTrackingResult;
@@ -473,8 +475,10 @@ private void revokeGrantRecord(
473475

474476
ms.persistStorageIntegrationIfNeededInCurrentTxn(callCtx, catalog, integration);
475477

476-
if (callCtx.getEntityMutationEngine() != null) {
477-
catalog = callCtx.getEntityMutationEngine().applyMutations(catalog);
478+
// CATALOG_PRE_PERSIST mutation point
479+
EntityMutationEngine entityMutationEngine = callCtx.getEntityMutationEngine();
480+
if (entityMutationEngine != null) {
481+
entityMutationEngine.applyMutations(MutationPoint.CATALOG_PRE_PERSIST, catalog);
478482
}
479483

480484
// now create and persist new catalog entity

polaris-core/src/testFixtures/java/org/apache/polaris/core/identity/mutation/NoOpEntityMutationEngine.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
public class NoOpEntityMutationEngine implements EntityMutationEngine {
3030

3131
@Override
32-
public PolarisBaseEntity applyMutations(PolarisBaseEntity entity) {
32+
public PolarisBaseEntity applyMutations(MutationPoint mutationPoint, PolarisBaseEntity entity) {
3333
return entity;
3434
}
3535
}

quarkus/admin/src/main/java/org/apache/polaris/admintool/config/QuarkusProducers.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ PolarisStorageIntegration<T> getStorageIntegrationForConfig(
7575
@Produces
7676
public EntityMutationEngine entityMutationEngine() {
7777
// An entity mutation engine is not required when running the admin tool.
78-
return entity -> entity;
78+
return ((mutationPoint, entity) -> entity);
7979
}
8080

8181
@Produces

quarkus/service/src/main/java/org/apache/polaris/service/quarkus/identity/mutation/CatalogEntityConnectionConfigMutator.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
import org.apache.polaris.core.entity.PolarisBaseEntity;
3030
import org.apache.polaris.core.identity.ServiceIdentityType;
3131
import org.apache.polaris.core.identity.dpo.ServiceIdentityInfoDpo;
32+
import org.apache.polaris.service.identity.mutation.AppliesTo;
3233
import org.apache.polaris.core.identity.mutation.EntityMutator;
34+
import org.apache.polaris.core.identity.mutation.MutationPoint;
3335
import org.apache.polaris.core.identity.registry.ServiceIdentityRegistry;
3436

3537
/**
@@ -41,6 +43,7 @@
4143
*/
4244
@RequestScoped
4345
@Identifier("catalog-connection-config")
46+
@AppliesTo(MutationPoint.CATALOG_PRE_PERSIST)
4447
public class CatalogEntityConnectionConfigMutator implements EntityMutator {
4548

4649
private final ServiceIdentityRegistry serviceIdentityRegistry;

quarkus/service/src/main/java/org/apache/polaris/service/quarkus/identity/mutation/NoOpEntityMutator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import jakarta.enterprise.context.ApplicationScoped;
2424
import org.apache.polaris.core.entity.PolarisBaseEntity;
2525
import org.apache.polaris.core.identity.mutation.EntityMutator;
26+
import org.apache.polaris.service.identity.mutation.AppliesTo;
2627

2728
/**
2829
* A no-op implementation of {@link EntityMutator} that returns the entity unchanged.

quarkus/service/src/test/java/org/apache/polaris/service/quarkus/auth/JWTSymmetricKeyGeneratorTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ public class JWTSymmetricKeyGeneratorTest {
4545
/** Sanity test to verify that we can generate a token */
4646
@Test
4747
public void testJWTSymmetricKeyGenerator() {
48-
PolarisCallContext polarisCallContext = new PolarisCallContext(null, null, null, null, null, null);
48+
PolarisCallContext polarisCallContext =
49+
new PolarisCallContext(null, null, null, null, null, null);
4950
PolarisMetaStoreManager metastoreManager = Mockito.mock(PolarisMetaStoreManager.class);
5051
String mainSecret = "test_secret";
5152
String clientId = "test_client_id";
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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+
20+
package org.apache.polaris.service.identity.mutation;
21+
22+
import jakarta.enterprise.util.AnnotationLiteral;
23+
import jakarta.inject.Qualifier;
24+
import java.lang.annotation.*;
25+
import org.apache.polaris.core.identity.mutation.EntityMutationEngine;
26+
import org.apache.polaris.core.identity.mutation.EntityMutator;
27+
import org.apache.polaris.core.identity.mutation.MutationPoint;
28+
29+
/**
30+
* Qualifier to mark an {@link EntityMutator} as applicable to a specific {@link MutationPoint}.
31+
*
32+
* <p>This is used by the {@link EntityMutationEngine} to apply only relevant mutators based on
33+
* context.
34+
*
35+
* <p>Supports being repeated on the same class to handle multiple mutation points.
36+
*/
37+
@Qualifier
38+
@Target(ElementType.TYPE)
39+
@Retention(RetentionPolicy.RUNTIME)
40+
@Repeatable(AppliesTos.class)
41+
public @interface AppliesTo {
42+
43+
/** The mutation point this mutator applies to. */
44+
MutationPoint value();
45+
46+
/** Helper for creating {@link AppliesTo} qualifiers programmatically. */
47+
final class Literal extends AnnotationLiteral<AppliesTo> implements AppliesTo {
48+
private final MutationPoint value;
49+
50+
public static Literal of(MutationPoint value) {
51+
return new Literal(value);
52+
}
53+
54+
private Literal(MutationPoint value) {
55+
this.value = value;
56+
}
57+
58+
@Override
59+
public MutationPoint value() {
60+
return value;
61+
}
62+
}
63+
}
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+
20+
package org.apache.polaris.service.identity.mutation;
21+
22+
import java.lang.annotation.ElementType;
23+
import java.lang.annotation.Retention;
24+
import java.lang.annotation.RetentionPolicy;
25+
import java.lang.annotation.Target;
26+
import org.apache.polaris.core.identity.mutation.EntityMutator;
27+
import org.apache.polaris.core.identity.mutation.MutationPoint;
28+
29+
/**
30+
* Container annotation for repeating {@link AppliesTo}.
31+
*
32+
* <p>Allows an {@link EntityMutator} to declare support for multiple {@link MutationPoint}s.
33+
*/
34+
@Target(ElementType.TYPE)
35+
@Retention(RetentionPolicy.RUNTIME)
36+
public @interface AppliesTos {
37+
AppliesTo[] value();
38+
}

service/common/src/main/java/org/apache/polaris/service/identity/mutation/EntityMutationEngineImpl.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
import jakarta.enterprise.inject.Instance;
2626
import jakarta.inject.Inject;
2727
import java.util.List;
28+
import java.util.Objects;
2829
import org.apache.polaris.core.entity.PolarisBaseEntity;
2930
import org.apache.polaris.core.identity.mutation.EntityMutationEngine;
3031
import org.apache.polaris.core.identity.mutation.EntityMutator;
32+
import org.apache.polaris.core.identity.mutation.MutationPoint;
3133

3234
@ApplicationScoped
3335
public class EntityMutationEngineImpl implements EntityMutationEngine {
@@ -43,26 +45,28 @@ public EntityMutationEngineImpl(
4345
}
4446

4547
@Override
46-
public PolarisBaseEntity applyMutations(PolarisBaseEntity entity) {
48+
public PolarisBaseEntity applyMutations(MutationPoint mutationPoint, PolarisBaseEntity entity) {
4749
PolarisBaseEntity result = entity;
4850

51+
// Collect mutators in configured order, filtering only those applicable to the mutation point
4952
List<EntityMutator> orderedMutators =
50-
// config.map(EntityMutationConfiguration::mutators).orElse(List.of()).stream()
5153
config.mutators().orElse(List.of()).stream()
5254
.map(
5355
id -> {
56+
// Resolve the mutator instance by ID
5457
Instance<EntityMutator> matched =
5558
mutatorInstances.select(Identifier.Literal.of(id));
56-
if (matched.isResolvable()) {
57-
return matched.get();
58-
} else {
59+
if (!matched.isResolvable()) {
5960
throw new IllegalStateException("No EntityMutator found for ID: " + id);
6061
}
62+
// Filter by MutationPoint via @AppliesTo
63+
Instance<EntityMutator> filtered =
64+
matched.select(AppliesTo.Literal.of(mutationPoint));
65+
return filtered.isResolvable() ? filtered.get() : null;
6166
})
67+
.filter(Objects::nonNull)
6268
.toList();
6369

64-
// Apply all mutators to the entity
65-
// TODO: Add a way to control the order of mutators
6670
for (EntityMutator mutator : orderedMutators) {
6771
result = mutator.apply(result);
6872
}

0 commit comments

Comments
 (0)