dimas-b commented on code in PR #4269: URL: https://github.com/apache/polaris/pull/4269#discussion_r3243029335
########## runtime/service/src/main/java/org/apache/polaris/service/idempotency/IdempotencyHandlerSupport.java: ########## @@ -0,0 +1,452 @@ +/* + * 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.idempotency; + +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.core.HttpHeaders; +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.Locale; +import java.util.Optional; +import java.util.TreeSet; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.regex.Pattern; +import org.apache.polaris.core.auth.PolarisPrincipal; +import org.apache.polaris.core.entity.IdempotencyRecord; +import org.apache.polaris.core.persistence.IdempotencyPersistence; +import org.apache.polaris.core.persistence.MetaStoreManagerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Reusable helper for handler-level idempotency. + * + * <p>Encapsulates the parts that are independent of any specific handler method: idempotency-key + * validation (UUID v7), principal/resource hashing, executor id resolution, polling for in-progress + * duplicates, and thin wrappers around the {@link IdempotencyPersistence} reserve / cancel / + * finalize operations. + * + * <p>All configuration (whether the feature is on, header name, executor identity, TTLs, + * in-progress wait, lease TTL) comes from {@link IdempotencyConfiguration} via CDI as a single + * deployment-wide source. Per-realm or per-catalog overrides are intentionally not modelled in this + * iteration; if operators need them later they can be added without changing handler-side call + * shapes. + * + * <p>The actual persistence is sourced per-realm from {@link + * MetaStoreManagerFactory#getOrCreateIdempotencyPersistence(org.apache.polaris.core.context.RealmContext)}, + * which each backend implements independently of {@link + * org.apache.polaris.core.persistence.BasePersistence}. + * + * <p>The handler decides what to do on each {@link Outcome}: for {@link Outcome#owned()} it + * executes the operation and finalizes; for {@link Outcome#duplicate(IdempotencyRecord)} it + * rebuilds the response from authoritative state (no stored response replay). + */ +@ApplicationScoped +public class IdempotencyHandlerSupport { + + private static final Logger LOGGER = LoggerFactory.getLogger(IdempotencyHandlerSupport.class); + + // RFC 9562 UUID v7 has version nibble 7 in time_hi_and_version. + private static final Pattern UUID_V7_PATTERN = + Pattern.compile( + "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", + Pattern.CASE_INSENSITIVE); + + @Inject IdempotencyConfiguration configuration; + @Inject MetaStoreManagerFactory metaStoreManagerFactory; + @Inject Clock clock; + + private final AtomicReference<String> resolvedExecutorId = new AtomicReference<>(); + + /** + * Test-only factory that builds an instance without going through CDI. Tests pass a {@code + * persistenceLookup} function returning the {@link IdempotencyPersistence} for a given realm id; + * production code goes through {@link MetaStoreManagerFactory#getOrCreateSession}. + */ + public static IdempotencyHandlerSupport forTesting( + IdempotencyConfiguration configuration, + Function<String, IdempotencyPersistence> persistenceLookup, + Clock clock) { + IdempotencyHandlerSupport support = new IdempotencyHandlerSupport(); + support.configuration = configuration; + support.clock = clock; + support.testPersistenceLookup = persistenceLookup; + return support; + } + + // Test-only override; null in production. + private Function<String, IdempotencyPersistence> testPersistenceLookup; + + /** Returns true if handler-level idempotency is enabled. */ + public boolean isEnabled() { + return configuration.enabled(); Review Comment: Following up on today's Community Sync discussion, would it make sense to an `.enabled()` method to `IdempotencyPersistence` instead of this config flag? -- 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]
