Skip to content

Commit

Permalink
jakartaee/persistence#454 - introduce FindOption interface and new ov…
Browse files Browse the repository at this point in the history
…erloads of EM.find()

 * implemented public <T> T find(Class<T> entityClass, Object primaryKey, FindOption... options)
 * added FindOption processing for JPA API enums, Timeout class was not added yet

Signed-off-by: Tomáš Kraus <tomas.kraus@oracle.com>
  • Loading branch information
Tomas-Kraus authored and lukasj committed Oct 17, 2023
1 parent e9fe59b commit 2b5bf90
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 36 deletions.
5 changes: 5 additions & 0 deletions jpa/org.eclipse.persistence.jpa/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@
<artifactId>ant</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <E> boolean isLoaded(E e, Attribute<? super E, ?> attribute) {
throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet");
public <E> boolean isLoaded(E entity, Attribute<? super E, ?> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -831,15 +831,18 @@ public <T> T find(Class<T> 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> T find(Class<T> 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> T find(EntityGraph<T> entityGraph, Object primaryKey, FindOption... options) {
FindOptionUtils.Options parsedOptions = FindOptionUtils.parse(options);
throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
* <p>Currently supported implementations:<ul>
* <li>{@link LockModeType}</li>
* <li>{@link CacheRetrieveMode}</li>
* <li>{@link CacheStoreMode}</li>
* <li>{@link PessimisticLockScope}</li>
* <li>{@link Timeout}</li>
* </ul>
*/
class FindOptionUtils {

/**
* Parsed {@link FindOption} array.
*/
record Options (LockModeType lockModeType, Map<String, Object> properties) {
}

// TODO-API-3.2 - FindOption not defined in EclipseLink scope, just in API.
private static final class OptionsBuilder {

private static final Map<Class<? extends FindOption>, BiConsumer<OptionsBuilder, FindOption>> ACTIONS = initActions();
// Tune this value to be at least number of supported FindOption classes.
private static final int MAP_CAPACITY = 8;

private final Map<String, Object> 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<Class<? extends FindOption>, BiConsumer<OptionsBuilder, FindOption>> initActions() {
Map<Class<? extends FindOption>, BiConsumer<OptionsBuilder, FindOption>> 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<String, Object> 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<String, Object> 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<OptionsBuilder, FindOption> 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<String, Object> properties, FindOption... options) {
return OptionsBuilder.build(properties, options);
}

}
Original file line number Diff line number Diff line change
@@ -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 {
}
*/

}

0 comments on commit 2b5bf90

Please sign in to comment.