adutra commented on code in PR #3293:
URL: https://github.com/apache/polaris/pull/3293#discussion_r2665275658


##########
runtime/service/src/main/java/org/apache/polaris/service/events/AllowedAttributeTypes.java:
##########
@@ -0,0 +1,134 @@
+/*
+ * 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.service.events;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.iceberg.TableMetadata;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.iceberg.rest.requests.CommitTransactionRequest;
+import org.apache.iceberg.rest.requests.CreateNamespaceRequest;
+import org.apache.iceberg.rest.requests.CreateTableRequest;
+import org.apache.iceberg.rest.requests.CreateViewRequest;
+import org.apache.iceberg.rest.requests.RegisterTableRequest;
+import org.apache.iceberg.rest.requests.RenameTableRequest;
+import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
+import org.apache.iceberg.rest.requests.UpdateTableRequest;
+import org.apache.iceberg.rest.responses.ConfigResponse;
+import org.apache.iceberg.rest.responses.LoadTableResponse;
+import org.apache.iceberg.rest.responses.LoadViewResponse;
+import org.apache.iceberg.rest.responses.UpdateNamespacePropertiesResponse;
+import org.apache.iceberg.view.ViewMetadata;
+import org.apache.polaris.core.admin.model.AddGrantRequest;
+import org.apache.polaris.core.admin.model.Catalog;
+import org.apache.polaris.core.admin.model.CatalogRole;
+import org.apache.polaris.core.admin.model.CreatePrincipalRequest;
+import org.apache.polaris.core.admin.model.CreatePrincipalRoleRequest;
+import org.apache.polaris.core.admin.model.GrantResource;
+import org.apache.polaris.core.admin.model.Principal;
+import org.apache.polaris.core.admin.model.PrincipalRole;
+import org.apache.polaris.core.admin.model.PrincipalWithCredentials;
+import org.apache.polaris.core.admin.model.RevokeGrantRequest;
+import org.apache.polaris.core.admin.model.UpdateCatalogRequest;
+import org.apache.polaris.core.admin.model.UpdateCatalogRoleRequest;
+import org.apache.polaris.core.admin.model.UpdatePrincipalRequest;
+import org.apache.polaris.core.admin.model.UpdatePrincipalRoleRequest;
+import org.apache.polaris.core.entity.PolarisPrivilege;
+import org.apache.polaris.service.types.AttachPolicyRequest;
+import org.apache.polaris.service.types.CommitViewRequest;
+import org.apache.polaris.service.types.CreateGenericTableRequest;
+import org.apache.polaris.service.types.CreatePolicyRequest;
+import org.apache.polaris.service.types.DetachPolicyRequest;
+import org.apache.polaris.service.types.GenericTable;
+import org.apache.polaris.service.types.GetApplicablePoliciesResponse;
+import org.apache.polaris.service.types.LoadPolicyResponse;
+import org.apache.polaris.service.types.NotificationRequest;
+import org.apache.polaris.service.types.UpdatePolicyRequest;
+
+/** Whitelist of types allowed for event attributes. */
+final class AllowedAttributeTypes {
+  private AllowedAttributeTypes() {}
+
+  static final Set<Class<?>> ALLOWED_TYPES =
+      Set.of(
+          // Primitives
+          String.class,
+          Boolean.class,
+          Integer.class,
+          Long.class,
+          Double.class,
+          // Collections
+          List.class,

Review Comment:
   This is probably too loose. We are not checking the collections element 
types here, so one could include a list or a set of any element type, 
effectively beating the purpose of this class in the first place. I would 
suggest to also check the element type and the map's key and value types: these 
should conform to the list specified here as well.



##########
runtime/service/src/main/java/org/apache/polaris/service/events/AllowedAttributeTypes.java:
##########
@@ -0,0 +1,134 @@
+/*
+ * 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.service.events;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.iceberg.TableMetadata;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.iceberg.rest.requests.CommitTransactionRequest;
+import org.apache.iceberg.rest.requests.CreateNamespaceRequest;
+import org.apache.iceberg.rest.requests.CreateTableRequest;
+import org.apache.iceberg.rest.requests.CreateViewRequest;
+import org.apache.iceberg.rest.requests.RegisterTableRequest;
+import org.apache.iceberg.rest.requests.RenameTableRequest;
+import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
+import org.apache.iceberg.rest.requests.UpdateTableRequest;
+import org.apache.iceberg.rest.responses.ConfigResponse;
+import org.apache.iceberg.rest.responses.LoadTableResponse;
+import org.apache.iceberg.rest.responses.LoadViewResponse;
+import org.apache.iceberg.rest.responses.UpdateNamespacePropertiesResponse;
+import org.apache.iceberg.view.ViewMetadata;
+import org.apache.polaris.core.admin.model.AddGrantRequest;
+import org.apache.polaris.core.admin.model.Catalog;
+import org.apache.polaris.core.admin.model.CatalogRole;
+import org.apache.polaris.core.admin.model.CreatePrincipalRequest;
+import org.apache.polaris.core.admin.model.CreatePrincipalRoleRequest;
+import org.apache.polaris.core.admin.model.GrantResource;
+import org.apache.polaris.core.admin.model.Principal;
+import org.apache.polaris.core.admin.model.PrincipalRole;
+import org.apache.polaris.core.admin.model.PrincipalWithCredentials;
+import org.apache.polaris.core.admin.model.RevokeGrantRequest;
+import org.apache.polaris.core.admin.model.UpdateCatalogRequest;
+import org.apache.polaris.core.admin.model.UpdateCatalogRoleRequest;
+import org.apache.polaris.core.admin.model.UpdatePrincipalRequest;
+import org.apache.polaris.core.admin.model.UpdatePrincipalRoleRequest;
+import org.apache.polaris.core.entity.PolarisPrivilege;
+import org.apache.polaris.service.types.AttachPolicyRequest;
+import org.apache.polaris.service.types.CommitViewRequest;
+import org.apache.polaris.service.types.CreateGenericTableRequest;
+import org.apache.polaris.service.types.CreatePolicyRequest;
+import org.apache.polaris.service.types.DetachPolicyRequest;
+import org.apache.polaris.service.types.GenericTable;
+import org.apache.polaris.service.types.GetApplicablePoliciesResponse;
+import org.apache.polaris.service.types.LoadPolicyResponse;
+import org.apache.polaris.service.types.NotificationRequest;
+import org.apache.polaris.service.types.UpdatePolicyRequest;
+
+/** Whitelist of types allowed for event attributes. */
+final class AllowedAttributeTypes {
+  private AllowedAttributeTypes() {}
+
+  static final Set<Class<?>> ALLOWED_TYPES =
+      Set.of(
+          // Primitives
+          String.class,
+          Boolean.class,
+          Integer.class,
+          Long.class,
+          Double.class,

Review Comment:
   Missing Float? Also: we could probably authorize any type that extends 
`Number`, including `BigInteger` and `BigDecimal`.



##########
runtime/service/src/main/java/org/apache/polaris/service/events/AttributeKey.java:
##########
@@ -0,0 +1,79 @@
+/*
+ * 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.service.events;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+import java.util.Objects;
+
+/**
+ * A type-safe key for event attributes. This allows for strongly-typed 
attribute access while
+ * maintaining flexibility for custom attributes.
+ *
+ * <p>Attribute types are validated at key creation time to ensure they can be 
serialized. See
+ * {@link AllowedAttributeTypes} for the list of allowed types.
+ *
+ * @param <T> the type of the attribute value
+ */
+public final class AttributeKey<T> {
+  private final String name;
+  private final Class<T> type;

Review Comment:
   I suggest that we use Guava's `com.google.common.reflect.TypeToken` here, as 
it handles generic types as well.



##########
runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEvent.java:
##########
@@ -18,10 +18,66 @@
  */
 package org.apache.polaris.service.events;
 
-/** Represents an event emitted by Polaris. */
-public interface PolarisEvent {
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
 
-  PolarisEventType type();
+/**
+ * Represents an event emitted by Polaris. Events have a type, metadata, and a 
map of typed
+ * attributes. Use {@link #builder(PolarisEventType, PolarisEventMetadata)} to 
create instances.
+ */
+public record PolarisEvent(
+    PolarisEventType type, PolarisEventMetadata metadata, Map<AttributeKey<?>, 
Object> attributes) {
+
+  public PolarisEvent {
+    attributes = Collections.unmodifiableMap(new HashMap<>(attributes));

Review Comment:
   ```suggestion
       attributes = Map.copyOf(attributes);
   ```



##########
runtime/service/src/main/java/org/apache/polaris/service/events/EventAttributes.java:
##########
@@ -0,0 +1,250 @@
+/*
+ * 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.service.events;
+
+import java.util.Map;
+import org.apache.iceberg.TableMetadata;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.iceberg.rest.requests.CommitTransactionRequest;
+import org.apache.iceberg.rest.requests.CreateNamespaceRequest;
+import org.apache.iceberg.rest.requests.CreateTableRequest;
+import org.apache.iceberg.rest.requests.CreateViewRequest;
+import org.apache.iceberg.rest.requests.RegisterTableRequest;
+import org.apache.iceberg.rest.requests.RenameTableRequest;
+import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
+import org.apache.iceberg.rest.requests.UpdateTableRequest;
+import org.apache.iceberg.rest.responses.ConfigResponse;
+import org.apache.iceberg.rest.responses.LoadTableResponse;
+import org.apache.iceberg.rest.responses.LoadViewResponse;
+import org.apache.iceberg.rest.responses.UpdateNamespacePropertiesResponse;
+import org.apache.iceberg.view.ViewMetadata;
+import org.apache.polaris.core.admin.model.AddGrantRequest;
+import org.apache.polaris.core.admin.model.Catalog;
+import org.apache.polaris.core.admin.model.CatalogRole;
+import org.apache.polaris.core.admin.model.CreatePrincipalRequest;
+import org.apache.polaris.core.admin.model.CreatePrincipalRoleRequest;
+import org.apache.polaris.core.admin.model.GrantResource;
+import org.apache.polaris.core.admin.model.Principal;
+import org.apache.polaris.core.admin.model.PrincipalRole;
+import org.apache.polaris.core.admin.model.PrincipalWithCredentials;
+import org.apache.polaris.core.admin.model.RevokeGrantRequest;
+import org.apache.polaris.core.admin.model.UpdateCatalogRequest;
+import org.apache.polaris.core.admin.model.UpdateCatalogRoleRequest;
+import org.apache.polaris.core.admin.model.UpdatePrincipalRequest;
+import org.apache.polaris.core.admin.model.UpdatePrincipalRoleRequest;
+import org.apache.polaris.core.entity.PolarisPrivilege;
+import org.apache.polaris.service.types.AttachPolicyRequest;
+import org.apache.polaris.service.types.CommitViewRequest;
+import org.apache.polaris.service.types.CreateGenericTableRequest;
+import org.apache.polaris.service.types.CreatePolicyRequest;
+import org.apache.polaris.service.types.DetachPolicyRequest;
+import org.apache.polaris.service.types.GenericTable;
+import org.apache.polaris.service.types.GetApplicablePoliciesResponse;
+import org.apache.polaris.service.types.LoadPolicyResponse;
+import org.apache.polaris.service.types.NotificationRequest;
+import org.apache.polaris.service.types.UpdatePolicyRequest;
+
+/**
+ * Standard attribute keys for Polaris events. These keys provide type-safe 
access to common event
+ * attributes and enable automatic pruning/filtering logic.
+ */
+public final class EventAttributes {
+  private EventAttributes() {}
+
+  // Catalog attributes
+  public static final AttributeKey<String> CATALOG_NAME =
+      AttributeKey.of("catalog_name", String.class);
+  public static final AttributeKey<Catalog> CATALOG = 
AttributeKey.of("catalog", Catalog.class);
+  public static final AttributeKey<UpdateCatalogRequest> 
UPDATE_CATALOG_REQUEST =
+      AttributeKey.of("update_catalog_request", UpdateCatalogRequest.class);
+
+  // Namespace attributes
+  public static final AttributeKey<Namespace> NAMESPACE =
+      AttributeKey.of("namespace", Namespace.class);
+  public static final AttributeKey<String> NAMESPACE_FQN =
+      AttributeKey.of("namespace_fqn", String.class);
+  public static final AttributeKey<String> PARENT_NAMESPACE_FQN =
+      AttributeKey.of("parent_namespace_fqn", String.class);
+  public static final AttributeKey<CreateNamespaceRequest> 
CREATE_NAMESPACE_REQUEST =
+      AttributeKey.of("create_namespace_request", 
CreateNamespaceRequest.class);
+  public static final AttributeKey<UpdateNamespacePropertiesRequest>
+      UPDATE_NAMESPACE_PROPERTIES_REQUEST =
+          AttributeKey.of(
+              "update_namespace_properties_request", 
UpdateNamespacePropertiesRequest.class);
+  public static final AttributeKey<UpdateNamespacePropertiesResponse>
+      UPDATE_NAMESPACE_PROPERTIES_RESPONSE =
+          AttributeKey.of(
+              "update_namespace_properties_response", 
UpdateNamespacePropertiesResponse.class);
+
+  @SuppressWarnings("unchecked")
+  public static final AttributeKey<Map<String, String>> NAMESPACE_PROPERTIES =
+      (AttributeKey<Map<String, String>>)
+          (AttributeKey<?>) AttributeKey.of("namespace_properties", Map.class);
+
+  // Table attributes
+  public static final AttributeKey<String> TABLE_NAME = 
AttributeKey.of("table_name", String.class);
+  public static final AttributeKey<TableIdentifier> TABLE_IDENTIFIER =
+      AttributeKey.of("table_identifier", TableIdentifier.class);
+  public static final AttributeKey<CreateTableRequest> CREATE_TABLE_REQUEST =
+      AttributeKey.of("create_table_request", CreateTableRequest.class);
+  public static final AttributeKey<UpdateTableRequest> UPDATE_TABLE_REQUEST =
+      AttributeKey.of("update_table_request", UpdateTableRequest.class);
+  public static final AttributeKey<RegisterTableRequest> 
REGISTER_TABLE_REQUEST =
+      AttributeKey.of("register_table_request", RegisterTableRequest.class);
+  public static final AttributeKey<RenameTableRequest> RENAME_TABLE_REQUEST =
+      AttributeKey.of("rename_table_request", RenameTableRequest.class);
+  public static final AttributeKey<TableMetadata> TABLE_METADATA_BEFORE =

Review Comment:
   My IDE is flagging some attributes as unused, which is intriguing. This is 
one of them.



##########
runtime/service/src/main/java/org/apache/polaris/service/events/AttributeKey.java:
##########
@@ -0,0 +1,79 @@
+/*
+ * 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.service.events;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+import java.util.Objects;
+
+/**
+ * A type-safe key for event attributes. This allows for strongly-typed 
attribute access while
+ * maintaining flexibility for custom attributes.
+ *
+ * <p>Attribute types are validated at key creation time to ensure they can be 
serialized. See
+ * {@link AllowedAttributeTypes} for the list of allowed types.
+ *
+ * @param <T> the type of the attribute value
+ */
+public final class AttributeKey<T> {

Review Comment:
   This class could be a record. Then the constructor could be public and we 
wouldn't need the static `of` method.
   
   The following should be enough (also introducing support for `TypeToken`):
   
   ```java
   public record AttributeKey<T>(@JsonValue String name, TypeToken<T> type) {
   
     public AttributeKey(String name, Class<T> type) {
       this(name, TypeToken.of(type));
     }
   
     public AttributeKey(String name, TypeToken<T> type) {
       this.name = Objects.requireNonNull(name, "name");
       this.type = Objects.requireNonNull(type, "type");
       if (!AllowedAttributeTypes.isAllowed(type)) {
         throw new IllegalArgumentException("Type " + type + " is not allowed 
for event attributes");
       }
     }
   }
   ```



##########
runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEvent.java:
##########
@@ -18,10 +18,66 @@
  */
 package org.apache.polaris.service.events;
 
-/** Represents an event emitted by Polaris. */
-public interface PolarisEvent {
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
 
-  PolarisEventType type();
+/**
+ * Represents an event emitted by Polaris. Events have a type, metadata, and a 
map of typed
+ * attributes. Use {@link #builder(PolarisEventType, PolarisEventMetadata)} to 
create instances.
+ */
+public record PolarisEvent(

Review Comment:
   FYI we can leverage the Java Immutables library to create the builder class 
for us. Here is how to do it:
   
   ```java
   @Value.Style(depluralize = true)
   public record PolarisEvent(
       PolarisEventType type, PolarisEventMetadata metadata, 
Map<AttributeKey<?>, Object> attributes) {
   
     @Builder.Constructor
     public PolarisEvent { ...  }
   
   }
   ```
   
   Then we can create an event like this:
   
   ```java
   PolarisEvent event =
     new PolarisEventBuilder()
         .type(PolarisEventType.BEFORE_LIMIT_REQUEST_RATE)
         .metadata(eventMetadataFactory.create())
         .putAttribute(EventAttributes.HTTP_METHOD, ctx.getMethod())
         .putAttribute(
             EventAttributes.REQUEST_URI, 
ctx.getUriInfo().getAbsolutePath().toString())
         .build();
   ```
   
   Another option is to transform the record into an interface and annotate 
with `@PolarisImmutable`.



##########
runtime/service/src/main/java/org/apache/polaris/service/events/AllowedAttributeTypes.java:
##########
@@ -0,0 +1,134 @@
+/*
+ * 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.service.events;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.iceberg.TableMetadata;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.iceberg.rest.requests.CommitTransactionRequest;
+import org.apache.iceberg.rest.requests.CreateNamespaceRequest;
+import org.apache.iceberg.rest.requests.CreateTableRequest;
+import org.apache.iceberg.rest.requests.CreateViewRequest;
+import org.apache.iceberg.rest.requests.RegisterTableRequest;
+import org.apache.iceberg.rest.requests.RenameTableRequest;
+import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
+import org.apache.iceberg.rest.requests.UpdateTableRequest;
+import org.apache.iceberg.rest.responses.ConfigResponse;
+import org.apache.iceberg.rest.responses.LoadTableResponse;
+import org.apache.iceberg.rest.responses.LoadViewResponse;
+import org.apache.iceberg.rest.responses.UpdateNamespacePropertiesResponse;
+import org.apache.iceberg.view.ViewMetadata;
+import org.apache.polaris.core.admin.model.AddGrantRequest;
+import org.apache.polaris.core.admin.model.Catalog;
+import org.apache.polaris.core.admin.model.CatalogRole;
+import org.apache.polaris.core.admin.model.CreatePrincipalRequest;
+import org.apache.polaris.core.admin.model.CreatePrincipalRoleRequest;
+import org.apache.polaris.core.admin.model.GrantResource;
+import org.apache.polaris.core.admin.model.Principal;
+import org.apache.polaris.core.admin.model.PrincipalRole;
+import org.apache.polaris.core.admin.model.PrincipalWithCredentials;
+import org.apache.polaris.core.admin.model.RevokeGrantRequest;
+import org.apache.polaris.core.admin.model.UpdateCatalogRequest;
+import org.apache.polaris.core.admin.model.UpdateCatalogRoleRequest;
+import org.apache.polaris.core.admin.model.UpdatePrincipalRequest;
+import org.apache.polaris.core.admin.model.UpdatePrincipalRoleRequest;
+import org.apache.polaris.core.entity.PolarisPrivilege;
+import org.apache.polaris.service.types.AttachPolicyRequest;
+import org.apache.polaris.service.types.CommitViewRequest;
+import org.apache.polaris.service.types.CreateGenericTableRequest;
+import org.apache.polaris.service.types.CreatePolicyRequest;
+import org.apache.polaris.service.types.DetachPolicyRequest;
+import org.apache.polaris.service.types.GenericTable;
+import org.apache.polaris.service.types.GetApplicablePoliciesResponse;
+import org.apache.polaris.service.types.LoadPolicyResponse;
+import org.apache.polaris.service.types.NotificationRequest;
+import org.apache.polaris.service.types.UpdatePolicyRequest;
+
+/** Whitelist of types allowed for event attributes. */
+final class AllowedAttributeTypes {
+  private AllowedAttributeTypes() {}
+
+  static final Set<Class<?>> ALLOWED_TYPES =
+      Set.of(
+          // Primitives
+          String.class,
+          Boolean.class,
+          Integer.class,
+          Long.class,
+          Double.class,
+          // Collections
+          List.class,
+          Map.class,
+          Set.class,
+          // Iceberg catalog types
+          Namespace.class,
+          TableIdentifier.class,
+          // Iceberg metadata types
+          TableMetadata.class,
+          ViewMetadata.class,
+          // Iceberg REST request types
+          CreateNamespaceRequest.class,

Review Comment:
   Maybe you could check if the type implements `RESTMessage`?



##########
runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEvent.java:
##########
@@ -18,10 +18,66 @@
  */
 package org.apache.polaris.service.events;
 
-/** Represents an event emitted by Polaris. */
-public interface PolarisEvent {
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
 
-  PolarisEventType type();
+/**
+ * Represents an event emitted by Polaris. Events have a type, metadata, and a 
map of typed
+ * attributes. Use {@link #builder(PolarisEventType, PolarisEventMetadata)} to 
create instances.
+ */
+public record PolarisEvent(
+    PolarisEventType type, PolarisEventMetadata metadata, Map<AttributeKey<?>, 
Object> attributes) {

Review Comment:
   Nit: exposing a `Map<AttributeKey<?>, Object>` parameter is a bit low-level. 
It would be better to introduce an `AttributeMap` class with type-safe 
operations for get and put. That's what Netty does, cf. 
`io.netty.util.AttributeMap`.
   
   Very simple impl suggestion:
   
   ```java
   public final class AttributeMap {
   
     private final Map<AttributeKey<?>, Object> attributes = new HashMap<>();
   
     public <T> void put(AttributeKey<T> key, T value) {
       attributes.put(key, value);
     }
   
     @SuppressWarnings("unchecked")
     public <T> T get(AttributeKey<T> key) {
       return (T) attributes.get(key);
     }
   }
   ```



##########
runtime/service/src/main/java/org/apache/polaris/service/events/AllowedAttributeTypes.java:
##########
@@ -0,0 +1,134 @@
+/*
+ * 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.service.events;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.iceberg.TableMetadata;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.iceberg.rest.requests.CommitTransactionRequest;
+import org.apache.iceberg.rest.requests.CreateNamespaceRequest;
+import org.apache.iceberg.rest.requests.CreateTableRequest;
+import org.apache.iceberg.rest.requests.CreateViewRequest;
+import org.apache.iceberg.rest.requests.RegisterTableRequest;
+import org.apache.iceberg.rest.requests.RenameTableRequest;
+import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
+import org.apache.iceberg.rest.requests.UpdateTableRequest;
+import org.apache.iceberg.rest.responses.ConfigResponse;
+import org.apache.iceberg.rest.responses.LoadTableResponse;
+import org.apache.iceberg.rest.responses.LoadViewResponse;
+import org.apache.iceberg.rest.responses.UpdateNamespacePropertiesResponse;
+import org.apache.iceberg.view.ViewMetadata;
+import org.apache.polaris.core.admin.model.AddGrantRequest;
+import org.apache.polaris.core.admin.model.Catalog;
+import org.apache.polaris.core.admin.model.CatalogRole;
+import org.apache.polaris.core.admin.model.CreatePrincipalRequest;
+import org.apache.polaris.core.admin.model.CreatePrincipalRoleRequest;
+import org.apache.polaris.core.admin.model.GrantResource;
+import org.apache.polaris.core.admin.model.Principal;
+import org.apache.polaris.core.admin.model.PrincipalRole;
+import org.apache.polaris.core.admin.model.PrincipalWithCredentials;
+import org.apache.polaris.core.admin.model.RevokeGrantRequest;
+import org.apache.polaris.core.admin.model.UpdateCatalogRequest;
+import org.apache.polaris.core.admin.model.UpdateCatalogRoleRequest;
+import org.apache.polaris.core.admin.model.UpdatePrincipalRequest;
+import org.apache.polaris.core.admin.model.UpdatePrincipalRoleRequest;
+import org.apache.polaris.core.entity.PolarisPrivilege;
+import org.apache.polaris.service.types.AttachPolicyRequest;
+import org.apache.polaris.service.types.CommitViewRequest;
+import org.apache.polaris.service.types.CreateGenericTableRequest;
+import org.apache.polaris.service.types.CreatePolicyRequest;
+import org.apache.polaris.service.types.DetachPolicyRequest;
+import org.apache.polaris.service.types.GenericTable;
+import org.apache.polaris.service.types.GetApplicablePoliciesResponse;
+import org.apache.polaris.service.types.LoadPolicyResponse;
+import org.apache.polaris.service.types.NotificationRequest;
+import org.apache.polaris.service.types.UpdatePolicyRequest;
+
+/** Whitelist of types allowed for event attributes. */
+final class AllowedAttributeTypes {
+  private AllowedAttributeTypes() {}
+
+  static final Set<Class<?>> ALLOWED_TYPES =
+      Set.of(
+          // Primitives
+          String.class,
+          Boolean.class,
+          Integer.class,
+          Long.class,
+          Double.class,
+          // Collections
+          List.class,
+          Map.class,
+          Set.class,
+          // Iceberg catalog types
+          Namespace.class,
+          TableIdentifier.class,
+          // Iceberg metadata types
+          TableMetadata.class,
+          ViewMetadata.class,
+          // Iceberg REST request types
+          CreateNamespaceRequest.class,

Review Comment:
   Here is a suggestion that leverages `TypeToken` in order to check element 
types of List, Set and Map:
   
   ```java
     static final Set<Class<?>> ALLOWED_TYPES =
         Set.of(
             String.class,
             Boolean.class,
             Number.class,
             RESTMessage.class,
             Namespace.class,
             TableIdentifier.class,
             TableMetadata.class,
             ViewMetadata.class,
             Catalog.class,
             Principal.class,
             PrincipalRole.class,
             PrincipalWithCredentials.class,
             CatalogRole.class,
             GrantResource.class,
             PolarisPrivilege.class,
             GenericTable.class);
   
     static boolean isAllowed(TypeToken<?> type) {
       Class<?> rawType = type.getRawType();
       if (rawType.equals(List.class)) {
         TypeToken<?> elementType = 
type.resolveType(List.class.getTypeParameters()[0]);
         return isSubtypeOfAllowedType(elementType.getRawType());
       } else if (rawType.equals(Set.class)) {
         TypeToken<?> elementType = 
type.resolveType(Set.class.getTypeParameters()[0]);
         return isSubtypeOfAllowedType(elementType.getRawType());
       } else if (rawType.equals(Map.class)) {
         TypeToken<?> keyType = 
type.resolveType(Map.class.getTypeParameters()[0]);
         TypeToken<?> valueType = 
type.resolveType(Map.class.getTypeParameters()[1]);
         return isSubtypeOfAllowedType(keyType.getRawType())
             && isSubtypeOfAllowedType(valueType.getRawType());
       } else {
         return isSubtypeOfAllowedType(rawType);
       }
     }
   
     private static boolean isSubtypeOfAllowedType(Class<?> rawType) {
       return ALLOWED_TYPES.stream().anyMatch(t -> t.isAssignableFrom(rawType));
     }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to