From 2b5bf90ebe992355f2a0fc34135e2db8214c483b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kraus?= Date: Thu, 31 Aug 2023 14:51:22 +0200 Subject: [PATCH] jakartaee/persistence#454 - introduce FindOption interface and new overloads of EM.find() * implemented public T find(Class entityClass, Object primaryKey, FindOption... options) * added FindOption processing for JPA API enums, Timeout class was not added yet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomáš Kraus --- jpa/org.eclipse.persistence.jpa/pom.xml | 5 + .../jpa/EntityManagerFactoryDelegate.java | 40 +---- .../internal/jpa/EntityManagerImpl.java | 7 +- .../internal/jpa/FindOptionUtils.java | 146 ++++++++++++++++++ .../internal/jpa/FindOptionUtilsTest.java | 48 ++++++ 5 files changed, 210 insertions(+), 36 deletions(-) create mode 100644 jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/FindOptionUtils.java create mode 100644 jpa/org.eclipse.persistence.jpa/src/test/java/org/eclipse/persistence/internal/jpa/FindOptionUtilsTest.java diff --git a/jpa/org.eclipse.persistence.jpa/pom.xml b/jpa/org.eclipse.persistence.jpa/pom.xml index 11534de6f31..e423e269052 100644 --- a/jpa/org.eclipse.persistence.jpa/pom.xml +++ b/jpa/org.eclipse.persistence.jpa/pom.xml @@ -124,6 +124,11 @@ ant true + + junit + junit + test + diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryDelegate.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryDelegate.java index c78590e0463..f258857852e 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryDelegate.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryDelegate.java @@ -723,50 +723,22 @@ public void setMetamodel(Metamodel aMetamodel) { this.setupImpl.setMetamodel(aMetamodel); } - /** - * Determine the load state of a given persistent attribute of an entity - * belonging to the persistence unit. - * - * @param entity - * containing the attribute - * @param attributeName - * name of attribute whose load state is to be determined - * @return false if entity's state has not been loaded or if the attribute - * state has not been loaded, otherwise true - */ @Override public boolean isLoaded(Object entity, String attributeName) { - if (Boolean.TRUE.equals(EntityManagerFactoryImpl.isLoaded(entity, attributeName, session))) { - return true; - } - return false; + return Boolean.TRUE.equals( + EntityManagerFactoryImpl.isLoaded(entity, attributeName, session)); } // TODO-API-3.2 @Override - public boolean isLoaded(E e, Attribute attribute) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + public boolean isLoaded(E entity, Attribute attribute) { + return isLoaded(entity, attribute.getName()); } - /** - * Determine the load state of an entity belonging to the persistence unit. - * This method can be used to determine the load state of an entity passed - * as a reference. An entity is considered loaded if all attributes for - * which FetchType EAGER has been specified have been loaded. The - * isLoaded(Object, String) method should be used to determine the load - * state of an attribute. Not doing so might lead to unintended loading of - * state. - * - * @param entity - * whose load state is to be determined - * @return false if the entity has not been loaded, else true. - */ @Override public boolean isLoaded(Object entity) { - if (Boolean.TRUE.equals(EntityManagerFactoryImpl.isLoaded(entity, session))) { - return true; - } - return false; + return Boolean.TRUE.equals( + EntityManagerFactoryImpl.isLoaded(entity, session)); } // TODO-API-3.2 diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerImpl.java index 0bdcae9a177..b54abe2b7c9 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerImpl.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerImpl.java @@ -831,15 +831,18 @@ public T find(Class entityClass, Object primaryKey, LockModeType lockMode } } - // TODO-API-3.2 + // TODO-API-3.2 - FindOption not defined in EclipseLink scope, just in API. @Override public T find(Class entityClass, Object primaryKey, FindOption... options) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + // Passing default find query hints, may be overwritten by options + FindOptionUtils.Options parsedOptions = FindOptionUtils.parse(getQueryHints(entityClass, OperationType.FIND), options); + return find(entityClass, primaryKey, parsedOptions.lockModeType(), parsedOptions.properties()); } // TODO-API-3.2 @Override public T find(EntityGraph entityGraph, Object primaryKey, FindOption... options) { + FindOptionUtils.Options parsedOptions = FindOptionUtils.parse(options); throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); } diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/FindOptionUtils.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/FindOptionUtils.java new file mode 100644 index 00000000000..e0e91fe6484 --- /dev/null +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/FindOptionUtils.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, + * or the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +// Contributors: +// 08/31/2023: Tomas Kraus +// - New Jakarta Persistence 3.2 Features +package org.eclipse.persistence.internal.jpa; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +import jakarta.persistence.CacheRetrieveMode; +import jakarta.persistence.CacheStoreMode; +import jakarta.persistence.FindOption; +import jakarta.persistence.LockModeType; +import jakarta.persistence.PessimisticLockScope; +import jakarta.persistence.Timeout; +import org.eclipse.persistence.config.QueryHints; + +/** + * {@link FindOption} processing tools. + *

Currently supported implementations:

    + *
  • {@link LockModeType}
  • + *
  • {@link CacheRetrieveMode}
  • + *
  • {@link CacheStoreMode}
  • + *
  • {@link PessimisticLockScope}
  • + *
  • {@link Timeout}
  • + *
+ */ +class FindOptionUtils { + + /** + * Parsed {@link FindOption} array. + */ + record Options (LockModeType lockModeType, Map properties) { + } + + // TODO-API-3.2 - FindOption not defined in EclipseLink scope, just in API. + private static final class OptionsBuilder { + + private static final Map, BiConsumer> ACTIONS = initActions(); + // Tune this value to be at least number of supported FindOption classes. + private static final int MAP_CAPACITY = 8; + + private final Map properties; + private LockModeType lockModeType; + + // FindOption is mostly implemented by enums so Map based dispatcher may be faster than if statements with instanceof. + private static Map, BiConsumer> initActions() { + Map, BiConsumer> actions = new HashMap<>(MAP_CAPACITY); + actions.put(LockModeType.class, OptionsBuilder::lockModeType); + actions.put(CacheRetrieveMode.class, OptionsBuilder::cacheRetrieveMode); + actions.put(CacheStoreMode.class, OptionsBuilder::cacheStoreMode); + actions.put(PessimisticLockScope.class, OptionsBuilder::pessimisticLockScope); + actions.put(Timeout.class, OptionsBuilder::timeout); + return actions; + } + + private OptionsBuilder(Map properties) { + this.lockModeType = null; + if (properties != null) { + this.properties = new HashMap<>(properties.size() + MAP_CAPACITY); + this.properties.putAll(properties); + } else { + this.properties = new HashMap<>(MAP_CAPACITY); + } + } + + // Dispatch rules Map is in static content so all handlers must be static. + private static void lockModeType(OptionsBuilder builder, FindOption lockModeType) { + builder.lockModeType = (LockModeType) lockModeType; + } + + private static void cacheRetrieveMode(OptionsBuilder builder, FindOption cacheRetrieveMode) { + builder.properties.put(QueryHints.CACHE_RETRIEVE_MODE, cacheRetrieveMode); + } + + private static void cacheStoreMode(OptionsBuilder builder, FindOption cacheStoreMode) { + builder.properties.put(QueryHints.CACHE_STORE_MODE, cacheStoreMode); + } + + private static void pessimisticLockScope(OptionsBuilder builder, FindOption cacheStoreMode) { + builder.properties.put(QueryHints.PESSIMISTIC_LOCK_SCOPE, cacheStoreMode); + } + + private static void timeout(OptionsBuilder builder, FindOption timeout) { + builder.properties.put(QueryHints.QUERY_TIMEOUT, timeout); + } + + private static Options build(Map properties, FindOption... options) { + OptionsBuilder builder = new OptionsBuilder(properties); + for (FindOption option : options) { + // TODO: Java 21 will allow pattern matching for switch, e.g. case rules for individual types. + // Fast dispatch for known classes. + BiConsumer action = ACTIONS.get(option.getClass()); + if (action != null) { + action.accept(builder, option); + // Fallback dispatch for unknown classes that may still match rules. + // No need to handle enums because they can't be extended. + } else if (option instanceof Timeout) { + timeout(builder, option); + } else { + // TODO: Log unknown FindOption instance + } + } + // Parsed options shall be completely immutable. + return new Options(builder.lockModeType, Collections.unmodifiableMap(builder.properties)); + } + + } + + /** + * Parse provided {@link FindOption} array. + * + * @param options {@link FindOption} array + * @return {@link Options} instance with parsed array content + */ + static Options parse(FindOption... options) { + return OptionsBuilder.build(null, options); + } + + /** + * Parse provided {@link FindOption} array. + * Returned {@link Options} instance contains provided {@code properties} which + * may be overwritten by content of {@link FindOption} array. + * + * @param properties initial query properties + * @param options {@link FindOption} array + * @return {@link Options} instance with parsed array content + */ + static Options parse(Map properties, FindOption... options) { + return OptionsBuilder.build(properties, options); + } + +} diff --git a/jpa/org.eclipse.persistence.jpa/src/test/java/org/eclipse/persistence/internal/jpa/FindOptionUtilsTest.java b/jpa/org.eclipse.persistence.jpa/src/test/java/org/eclipse/persistence/internal/jpa/FindOptionUtilsTest.java new file mode 100644 index 00000000000..b00e468f7b4 --- /dev/null +++ b/jpa/org.eclipse.persistence.jpa/src/test/java/org/eclipse/persistence/internal/jpa/FindOptionUtilsTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, + * or the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +// Contributors: +// 08/31/2023: Tomas Kraus +// - New Jakarta Persistence 3.2 Features +package org.eclipse.persistence.internal.jpa; + +import jakarta.persistence.CacheRetrieveMode; +import jakarta.persistence.CacheStoreMode; +import jakarta.persistence.LockModeType; +import jakarta.persistence.PessimisticLockScope; +import junit.framework.TestCase; +import org.eclipse.persistence.config.QueryHints; + +/** + * + */ +public class FindOptionUtilsTest extends TestCase { + + public void testParseAllOptions() { + FindOptionUtils.Options parsed = FindOptionUtils.parse( + LockModeType.OPTIMISTIC, + CacheRetrieveMode.USE, + CacheStoreMode.REFRESH, + PessimisticLockScope.EXTENDED); + assertEquals(parsed.lockModeType(), LockModeType.OPTIMISTIC); + assertEquals(parsed.properties().get(QueryHints.CACHE_RETRIEVE_MODE), CacheRetrieveMode.USE); + assertEquals(parsed.properties().get(QueryHints.CACHE_STORE_MODE), CacheStoreMode.REFRESH); + assertEquals(parsed.properties().get(QueryHints.PESSIMISTIC_LOCK_SCOPE), PessimisticLockScope.EXTENDED); + } + + /* TODO-API-3.2: Add Timeout tests when API is fixed + private static final class CustomTimeout extends Timeout { + + } + */ + +}