diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/ExceptionLocalizationResource.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/ExceptionLocalizationResource.java index 8f8e3bfc608..28e799a007d 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/ExceptionLocalizationResource.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/ExceptionLocalizationResource.java @@ -254,6 +254,8 @@ public class ExceptionLocalizationResource extends ListResourceBundle { { "getpersistenceunitutil_called_on_closed_emf", "getPersistenceUnitUtil() was called on a closed EntityManagerFactory."}, { "named_entity_graph_exists", "NamedEntityGraph with name {0} found on {1} already exists in this persistence unit."}, { "cannot_get_from_non_correlated_query", "getCorrelationParent() called on a from-clause that was not obtained through correlation." }, + { "no_key_in_entity", "Cannot add join of {0} to {1} because target class is missing attribute of source type"}, + { "RIGHT_JOIN_NOT_SUPPORTED", "Right join is not supported"}, { "wrap_convert_exception", "An exception occurred while calling {0} on converter class {1} with value {2}"}, { "ora_pessimistic_locking_with_rownum", "Pessimistic locking with query row limits is not supported."}, { "bean_validation_constraint_violated", "One or more Bean Validation constraints were violated while executing Automatic Bean Validation on callback event: {0} for class: {1}. Please refer to the embedded constraint violations for details."}, diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java index 855deba7ab1..1de9ac1d8b4 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java @@ -474,6 +474,13 @@ public class LoggingLocalizationResource extends ListResourceBundle { { "dbws_orm_metadata_read_error", "The [{0}] ORM metadata could not be read."}, { "dbws_oxm_metadata_read_error", "The [{0}] OXM metadata could not be read."}, { "dbws_no_wsdl_inline_schema", "The [{0}] WSDL inline schema could not be read."}, + // JPA 3.2 + { "unknown_cacheRetrieveMode_type", "Unknown {0} type of jakarta.persistence.cache.retrieveMode property"}, + { "unknown_cacheStoreMode_type", "Unknown {0} type of jakarta.persistence.cache.storeMode property"}, + { "unknown_queryTimeoutUnit_type", "Unknown {0} type of eclipselink.query.timeout.unit property"}, + { "unknown_queryTimeout_type", "Unknown {0} type of jakarta.persistence.query.timeout property"}, + { "error_queryTimeoutParse", "Could not parse jakarta.persistence.query.timeout property value {0}: {1}"}, + { "validate_object_space", "validate object space." }, { "stack_of_visited_objects_that_refer_to_the_corrupt_object", "stack of visited objects that refer to the corrupt object: {0}" }, { "corrupt_object_referenced_through_mapping", "The following corrupt object is referenced through mapping: {0}" }, diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/test/java/org/eclipse/persistence/testing/tests/jpa21/advanced/CriteriaQueryMetamodelTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/test/java/org/eclipse/persistence/testing/tests/jpa21/advanced/CriteriaQueryMetamodelTest.java index 3d28a6e70ae..bd7ff98c448 100644 --- a/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/test/java/org/eclipse/persistence/testing/tests/jpa21/advanced/CriteriaQueryMetamodelTest.java +++ b/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/test/java/org/eclipse/persistence/testing/tests/jpa21/advanced/CriteriaQueryMetamodelTest.java @@ -80,6 +80,7 @@ public static Test suite() { suite.addTest(new CriteriaQueryMetamodelTest("testMetamodelOnClause")); suite.addTest(new CriteriaQueryMetamodelTest("testMetamodelOnClauseOverCollection")); suite.addTest(new CriteriaQueryMetamodelTest("testMetamodelOnClauseWithLeftJoin")); + suite.addTest(new CriteriaQueryMetamodelTest("testMetamodelOnClauseWithLeftJoinOnClass")); suite.addTest(new CriteriaQueryMetamodelTest("simpleMetamodelCriteriaUpdateTest")); suite.addTest(new CriteriaQueryMetamodelTest("testMetamodelCriteriaUpdate")); suite.addTest(new CriteriaQueryMetamodelTest("testMetamodelComplexConditionCaseInCriteriaUpdate")); @@ -181,6 +182,34 @@ public void testMetamodelOnClauseWithLeftJoin() { } } + // Join directly on Address class must return the same results as join on attribute + public void testMetamodelOnClauseWithLeftJoinOnClass() { + EntityManager em = createEntityManager(); + Query query = em.createQuery("Select e from Employee e left join e.address a on a.city = 'Ottawa' " + + "where a.postalCode is not null"); + List baseResult = query.getResultList(); + + Metamodel metamodel = em.getMetamodel(); + EntityType entityEmp_ = metamodel.entity(Employee.class); + EntityType
entityAddr_ = metamodel.entity(Address.class); + + CriteriaBuilder qb = em.getCriteriaBuilder(); + CriteriaQuerycq = qb.createQuery(Employee.class); + Root root = cq.from(entityEmp_); + Join address = root.join(Address.class, JoinType.LEFT); + address.on(qb.equal(address.get(entityAddr_.getSingularAttribute("city", String.class)), "Ottawa")); + cq.where(qb.isNotNull(address.get(entityAddr_.getSingularAttribute("postalCode", String.class)))); + List testResult = em.createQuery(cq).getResultList(); + + clearCache(); + closeEntityManager(em); + + if (baseResult.size() != testResult.size()) { + fail("Criteria query using ON clause with a left join did not match JPQL results; " + +baseResult.size()+" were expected, while criteria query returned "+testResult.size()); + } + } + /////UPDATE Criteria tests: public void simpleMetamodelCriteriaUpdateTest() { diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EJBQueryImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EJBQueryImpl.java index fd842fdddde..8953db3049d 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EJBQueryImpl.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EJBQueryImpl.java @@ -41,8 +41,10 @@ import jakarta.persistence.Parameter; import jakarta.persistence.PersistenceException; import jakarta.persistence.TemporalType; +import jakarta.persistence.Timeout; import jakarta.persistence.TypedQuery; +import org.eclipse.persistence.config.QueryHints; import org.eclipse.persistence.exceptions.QueryException; import org.eclipse.persistence.expressions.Expression; import org.eclipse.persistence.internal.databaseaccess.DatasourcePlatform; @@ -54,6 +56,8 @@ import org.eclipse.persistence.internal.queries.JPQLCallQueryMechanism; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.jpa.JpaQuery; +import org.eclipse.persistence.logging.AbstractSessionLog; +import org.eclipse.persistence.logging.SessionLog; import org.eclipse.persistence.queries.Cursor; import org.eclipse.persistence.queries.DataReadQuery; import org.eclipse.persistence.queries.DatabaseQuery; @@ -296,43 +300,44 @@ public TypedQuery setHint(String hintName, Object value) { * if not a Java Persistence query language SELECT query */ @Override - public EJBQueryImpl setLockMode(LockModeType lockMode) { - return (EJBQueryImpl) super.setLockMode(lockMode); + @SuppressWarnings("unchecked") + public EJBQueryImpl setLockMode(LockModeType lockMode) { + return (EJBQueryImpl) super.setLockMode(lockMode); } - // TODO-API-3.2 + + // Based on EntityManagerImpl#getQueryHints(Object,OperationType) @Override public CacheRetrieveMode getCacheRetrieveMode() { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + return FindOptionUtils.getCacheRetrieveMode(getDatabaseQuery().getProperties()); } - // TODO-API-3.2 @Override public TypedQuery setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + FindOptionUtils.setCacheRetrieveMode(getDatabaseQuery().getProperties(), cacheRetrieveMode); + return this; } - // TODO-API-3.2 @Override public CacheStoreMode getCacheStoreMode() { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + return FindOptionUtils.getCacheStoreMode(getDatabaseQuery().getProperties()); } - // TODO-API-3.2 @Override public TypedQuery setCacheStoreMode(CacheStoreMode cacheStoreMode) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + FindOptionUtils.setCacheStoreMode(getDatabaseQuery().getProperties(), cacheStoreMode); + return this; } - // TODO-API-3.2 @Override public Integer getTimeout() { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + return FindOptionUtils.getTimeout(getDatabaseQuery().getProperties()); } @Override public TypedQuery setTimeout(Integer timeout) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + FindOptionUtils.setTimeout(getDatabaseQuery().getProperties(), timeout); + return this; } /** diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryProvider.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryProvider.java index 517c6e261ff..23ab03bcba4 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryProvider.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryProvider.java @@ -38,6 +38,7 @@ import java.util.Map; import org.eclipse.persistence.config.PersistenceUnitProperties; +import org.eclipse.persistence.config.QueryHints; import org.eclipse.persistence.config.TargetDatabase; import org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.TableCreationType; import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; @@ -85,7 +86,9 @@ public class EntityManagerFactoryProvider { {PersistenceUnitProperties.JDBC_PASSWORD , "eclipselink.jdbc.password"}, {PersistenceUnitProperties.WEAVING , "persistence.tools.weaving"}, {PersistenceUnitProperties.LOGGING_LEVEL + "." + SessionLog.METAMODEL, PersistenceUnitProperties.LOGGING_LEVEL + ".jpa_" + SessionLog.METAMODEL}, - {PersistenceUnitProperties.LOGGING_LEVEL + "." + SessionLog.METADATA, PersistenceUnitProperties.LOGGING_LEVEL + ".ejb_or_" + SessionLog.METADATA} + {PersistenceUnitProperties.LOGGING_LEVEL + "." + SessionLog.METADATA, PersistenceUnitProperties.LOGGING_LEVEL + ".ejb_or_" + SessionLog.METADATA}, + {QueryHints.CACHE_RETRIEVE_MODE, "jakarta.persistence.cacheRetrieveMode"}, + {QueryHints.CACHE_STORE_MODE, "jakarta.persistence.cacheStoreMode"} }; /** @@ -132,8 +135,16 @@ protected static void generateDefaultTables(SchemaManager mgr, TableCreationType * {@link System} property or {@code null} if property identified by {@code propertyKey} does not exist. */ public static String getConfigPropertyAsString(final String propertyKey, final Map overrides) { - final String value = overrides != null ? (String)overrides.get(propertyKey) : null; - return value != null ? value : PrivilegedAccessHelper.getSystemProperty(propertyKey); + Object value = overrides != null ? overrides.get(propertyKey) : null; + if (value != null) { + if (value instanceof String strValue) { + return strValue; + } else { + return value.toString(); + } + } else { + return PrivilegedAccessHelper.getSystemProperty(propertyKey); + } } /** 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 93a319de633..c20f1c3668e 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 @@ -935,6 +935,9 @@ protected Object findInternal(ClassDescriptor descriptor, AbstractSession sessio } } + // Translate deprecated properties to the current names + EntityManagerFactoryProvider.translateOldProperties(properties, this.databaseSession); + // Get the read object query and apply the properties to it. // PERF: use descriptor defined query to avoid extra query creation. ReadObjectQuery query = descriptor.getQueryManager().getReadObjectQuery(); @@ -956,8 +959,10 @@ protected Object findInternal(ClassDescriptor descriptor, AbstractSession sessio // Apply any EclipseLink defaults if they haven't been set through // the properties. - if (properties == null || ( !properties.containsKey(QueryHints.CACHE_USAGE) && !properties.containsKey(QueryHints.CACHE_RETRIEVE_MODE) && !properties.containsKey(QueryHints.CACHE_STORE_MODE) - && !properties.containsKey("jakarta.persistence.cacheRetrieveMode") && !properties.containsKey("jakarta.persistence.cacheStoreMode"))) { + if (properties == null || + ( !properties.containsKey(QueryHints.CACHE_USAGE) + && !properties.containsKey(QueryHints.CACHE_RETRIEVE_MODE) + && !properties.containsKey(QueryHints.CACHE_STORE_MODE))) { query.conformResultsInUnitOfWork(); } @@ -2911,28 +2916,24 @@ public LockModeType getLockMode(Object entity) { } } - // TODO-API-3.2 @Override public CacheRetrieveMode getCacheRetrieveMode() { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + return FindOptionUtils.getCacheRetrieveMode(properties); } - // TODO-API-3.2 @Override public void setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + FindOptionUtils.setCacheRetrieveMode(properties, cacheRetrieveMode); } - // TODO-API-3.2 @Override public CacheStoreMode getCacheStoreMode() { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + return FindOptionUtils.getCacheStoreMode(properties); } - // TODO-API-3.2 @Override public void setCacheStoreMode(CacheStoreMode cacheStoreMode) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + FindOptionUtils.setCacheStoreMode(properties, cacheStoreMode); } /** 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 index e0e91fe6484..ffd9ddab344 100644 --- 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 @@ -18,6 +18,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import jakarta.persistence.CacheRetrieveMode; @@ -26,7 +27,12 @@ import jakarta.persistence.LockModeType; import jakarta.persistence.PessimisticLockScope; import jakarta.persistence.Timeout; +import jakarta.persistence.TypedQuery; import org.eclipse.persistence.config.QueryHints; +import org.eclipse.persistence.internal.sessions.AbstractSession; +import org.eclipse.persistence.logging.AbstractSessionLog; +import org.eclipse.persistence.logging.SessionLog; +import org.eclipse.persistence.queries.DatabaseQuery; /** * {@link FindOption} processing tools. @@ -95,7 +101,8 @@ private static void pessimisticLockScope(OptionsBuilder builder, FindOption cach } private static void timeout(OptionsBuilder builder, FindOption timeout) { - builder.properties.put(QueryHints.QUERY_TIMEOUT, timeout); + builder.properties.put(QueryHints.QUERY_TIMEOUT_UNIT, java.util.concurrent.TimeUnit.MILLISECONDS); + builder.properties.put(QueryHints.QUERY_TIMEOUT, ((Timeout)timeout).milliseconds()); } private static Options build(Map properties, FindOption... options) { @@ -143,4 +150,90 @@ static Options parse(Map properties, FindOption... options) { return OptionsBuilder.build(properties, options); } + // Based on EntityManagerImpl#getQueryHints(Object,OperationType) + static CacheRetrieveMode getCacheRetrieveMode(Map properties) { + // QueryHints property + Object propertyValue = properties.get(QueryHints.CACHE_RETRIEVE_MODE); + if (propertyValue instanceof CacheRetrieveMode) { + return (CacheRetrieveMode) propertyValue; + } else if (propertyValue != null) { + AbstractSessionLog.getLog().log(SessionLog.WARNING, + SessionLog.QUERY, + "unknown_cacheRetrieveMode_type", + propertyValue.getClass().getName()); + } + // Default value according to JPA spec. + return CacheRetrieveMode.USE; + } + + @SuppressWarnings("unchecked") + static void setCacheRetrieveMode(Map properties, CacheRetrieveMode cacheRetrieveMode) { + ((Map)properties).put(QueryHints.CACHE_RETRIEVE_MODE, cacheRetrieveMode); + } + + static CacheStoreMode getCacheStoreMode(Map properties) { + // QueryHints property + Object propertyValue = properties.get(QueryHints.CACHE_STORE_MODE); + if (propertyValue instanceof CacheStoreMode) { + return (CacheStoreMode) propertyValue; + } else if (propertyValue != null) { + AbstractSessionLog.getLog().log(SessionLog.WARNING, + SessionLog.QUERY, + "unknown_cacheStoreMode_type", + propertyValue.getClass().getName()); + } + // Default value according to JPA spec. + return CacheStoreMode.USE; + } + + @SuppressWarnings("unchecked") + static void setCacheStoreMode(Map properties, CacheStoreMode cacheStoreMode) { + ((Map)properties).put(QueryHints.CACHE_STORE_MODE, cacheStoreMode); + } + + static Integer getTimeout(Map properties) { + // QueryHints.QUERY_TIMEOUT_UNIT may contain TimeUnit + TimeUnit timeUnit = TimeUnit.MILLISECONDS; + Object propertyValue = properties.get(QueryHints.QUERY_TIMEOUT_UNIT); + if (propertyValue instanceof TimeUnit) { + timeUnit = (TimeUnit)propertyValue; + } else if (propertyValue != null) { + AbstractSessionLog.getLog().log(SessionLog.WARNING, + SessionLog.QUERY, + "unknown_queryTimeoutUnit_type", + propertyValue.getClass().getName()); + } + // QueryHints.QUERY_TIMEOUT must be converted from actual units to milliseconds + propertyValue = properties.get(QueryHints.QUERY_TIMEOUT); + if (propertyValue instanceof Number n) { + return (int)TimeUnit.MILLISECONDS.convert(n.longValue(), timeUnit); + } else if (propertyValue instanceof String s) { + try { + long value = Long.parseLong(s); + return (int)TimeUnit.MILLISECONDS.convert(value, timeUnit); + } catch (NumberFormatException e) { + AbstractSessionLog.getLog().log(SessionLog.WARNING, + SessionLog.QUERY, + "error_queryTimeoutParse", + s, + e.getLocalizedMessage()); + } + } else { + AbstractSessionLog.getLog().log(SessionLog.WARNING, + SessionLog.QUERY, + "unknown_queryTimeout_type", + propertyValue.getClass().getName()); + } + // Return default value (means no timeout was set) + return null; + } + + @SuppressWarnings("unchecked") + static void setTimeout(Map properties, Integer timeout) { + // Javadoc does not specify units. Default QueryHints.QUERY_TIMEOUT unit is milliseconds + // so timeout argument is expected to be miliseconds too. + ((Map)properties).put(QueryHints.QUERY_TIMEOUT_UNIT, TimeUnit.MILLISECONDS); + ((Map)properties).put(QueryHints.QUERY_TIMEOUT, timeout); + } + } diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/StoredProcedureQueryImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/StoredProcedureQueryImpl.java index 3101a289dd8..2d7a23caec6 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/StoredProcedureQueryImpl.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/StoredProcedureQueryImpl.java @@ -883,37 +883,40 @@ public StoredProcedureQueryImpl setFlushMode(FlushModeType flushMode) { // TODO-API-3.2 @Override public CacheRetrieveMode getCacheRetrieveMode() { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + return FindOptionUtils.getCacheRetrieveMode(getDatabaseQuery().getProperties()); } // TODO-API-3.2 @Override public StoredProcedureQueryImpl setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + FindOptionUtils.setCacheRetrieveMode(getDatabaseQuery().getProperties(), cacheRetrieveMode); + return this; } // TODO-API-3.2 @Override public CacheStoreMode getCacheStoreMode() { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + return FindOptionUtils.getCacheStoreMode(getDatabaseQuery().getProperties()); } // TODO-API-3.2 @Override public StoredProcedureQueryImpl setCacheStoreMode(CacheStoreMode cacheStoreMode) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + FindOptionUtils.setCacheStoreMode(getDatabaseQuery().getProperties(), cacheStoreMode); + return this; } // TODO-API-3.2 @Override public Integer getTimeout() { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + return FindOptionUtils.getTimeout(getDatabaseQuery().getProperties()); } // TODO-API-3.2 @Override public StoredProcedureQueryImpl setTimeout(Integer timeout) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + FindOptionUtils.setTimeout(getDatabaseQuery().getProperties(), timeout); + return this; } /** diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/metamodel/MetamodelImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/metamodel/MetamodelImpl.java index 0dd848a0100..e63e1900974 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/metamodel/MetamodelImpl.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/metamodel/MetamodelImpl.java @@ -185,10 +185,35 @@ private void entityEmbeddableManagedTypeNotFound(Map typeMap, Object aType, Clas } } + private void entityEmbeddableManagedTypeNotFound(Map typeMap, Object aType, String entityName, String metamodelType, String metamodelTypeName) { + // 338837: verify that the collection is not empty - this would mean entities did not make it into the search path + if (typeMap.isEmpty()) { + AbstractSessionLog.getLog().log(SessionLog.WARNING, + SessionLog.METAMODEL, + "metamodel_type_collection_empty_during_lookup", + entityName, + metamodelTypeName); + } + if (null == aType) { + throw new IllegalArgumentException(ExceptionLocalization.buildMessage( + "metamodel_class_null_type_instance", + new Object[] {entityName, metamodelTypeName, metamodelType})); + } else { + throw new IllegalArgumentException(ExceptionLocalization.buildMessage( + "metamodel_class_incorrect_type_instance", + new Object[] {entityName, metamodelTypeName, aType})); + } + } + + // TODO-API-3.2 @Override public EntityType entity(String entityName) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + EntityTypeImpl aType = this.entities.get(entityName); + if (aType == null) { + entityEmbeddableManagedTypeNotFound(entities, null, entityName, "Entity", "EntityType"); + } + return aType; } /** diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaBuilderImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaBuilderImpl.java index 2cc0ca86316..db254a238c2 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaBuilderImpl.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaBuilderImpl.java @@ -889,7 +889,7 @@ public > Predicate between(Expression> buildList(Expression... expressions) { + protected static List> buildList(Expression... expressions) { // Immutable List causes test failures. // Those lists are usually small (size 1-2) and modifications are rare. Default list size is too much. List> list = new ArrayList<>(expressions.length + 2); diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/FromImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/FromImpl.java index 8f721e46fe3..08a28e15233 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/FromImpl.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/FromImpl.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -33,6 +34,7 @@ import jakarta.persistence.criteria.ListJoin; import jakarta.persistence.criteria.MapJoin; import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.SetJoin; import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.Attribute.PersistentAttributeType; @@ -47,7 +49,6 @@ import jakarta.persistence.metamodel.PluralAttribute.CollectionType; import jakarta.persistence.metamodel.SingularAttribute; import jakarta.persistence.metamodel.Type.PersistenceType; - import org.eclipse.persistence.internal.expressions.ObjectExpression; import org.eclipse.persistence.internal.helper.ClassConstants; import org.eclipse.persistence.internal.localization.ExceptionLocalization; @@ -525,28 +526,68 @@ public Join join(String attributeName, JoinType jt) { } } - // TODO-API-3.2 @Override public Join join(Class entityClass) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + return join(entityClass, JoinType.INNER); } - // TODO-API-3.2 + // Adds join for specified class. + // Initializes ON condition with all mapping attributes to be equal to entity @Id if such @Id exists @Override + @SuppressWarnings("unchecked") public Join join(Class entityClass, JoinType joinType) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + // Search target class (this) for SingularAttributes matching source class (entityClass) + JoinImpl join = null; + List equalPredicates = new LinkedList<>(); + for (Attribute attribute : ((ManagedType) managedType).getAttributes()) { + if ((attribute instanceof SingularAttribute) + && ((SingularAttribute) attribute).getBindableJavaType().isAssignableFrom(entityClass)) { + SingularAttribute singularAttribute = (SingularAttribute) attribute; + // Create Join instance if not exists + if (join == null) { + ObjectExpression exp = + ((ObjectExpression) this.currentNode).newDerivedExpressionNamed(singularAttribute.getName()); + switch (joinType) { + case LEFT: + exp.doUseOuterJoin(); + break; + case RIGHT: + throw new UnsupportedOperationException(ExceptionLocalization.buildMessage("RIGHT_JOIN_NOT_SUPPORTED")); + case INNER: + exp.doNotUseOuterJoin(); + } + join = new JoinImpl<>(this, managedType, this.metamodel, entityClass, exp, singularAttribute, joinType); + this.joins.add(join); + join.isJoin = true; + } + // Add current target class (this) SingularAttribute into equal predicates if source class has @Id + Expression keyExpr = get(singularAttribute); + equalPredicates.add(new CompoundExpressionImpl(metamodel, + ExpressionImpl.currentNode(keyExpr) + .notEqual(ExpressionImpl.currentNode(join)), + CriteriaBuilderImpl.buildList(keyExpr, join))); + } + } + if (join == null) { + throw new IllegalStateException( + ExceptionLocalization.buildMessage("no_key_in_entity", + new String[] { + entityClass.getName(), + this.managedType.getJavaType().getName()})); + } else { + // TODO: Need to write test for predicates generated by this method + return join.on(equalPredicates.toArray(new Predicate[equalPredicates.size()])); + } } - // TODO-API-3.2 @Override public Join join(EntityType entity) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + return join(entity, JoinType.INNER); } - // TODO-API-3.2 @Override public Join join(EntityType entity, JoinType joinType) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + return join(entity.getJavaType(), joinType); } @Override