Bug 570702: Add support for Embeddable fields as JOIN targets

Signed-off-by: Will Dazey <dazeydev.3@gmail.com>
diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/EclipseLinkSemanticValidatorHelper.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/EclipseLinkSemanticValidatorHelper.java
index 0778235..ede18b8 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/EclipseLinkSemanticValidatorHelper.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/EclipseLinkSemanticValidatorHelper.java
@@ -1,5 +1,6 @@
 /*
- * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021 IBM Corporation. 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
@@ -378,6 +379,11 @@
         }
     }
 
+    @Override
+    public boolean isEmbeddableMapping(Object mapping) {
+        return (mapping instanceof org.eclipse.persistence.mappings.EmbeddableMapping);
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/TestCollectionTableEmbeddable.java b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/TestCollectionTableEmbeddable.java
index 1d2bf6f..f6ea116 100644
--- a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/TestCollectionTableEmbeddable.java
+++ b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/TestCollectionTableEmbeddable.java
@@ -13,17 +13,25 @@
 
 import java.util.Date;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.EntityManagerFactory;
+import jakarta.persistence.Query;
 
 import org.eclipse.persistence.jpa.embeddable.model.ElementCollectionEmbeddableTemporal;
 import org.eclipse.persistence.jpa.embeddable.model.ElementCollectionEntity;
+import org.eclipse.persistence.jpa.embeddable.model.SpecAddress;
+import org.eclipse.persistence.jpa.embeddable.model.SpecContactInfo;
+import org.eclipse.persistence.jpa.embeddable.model.SpecEmployee;
+import org.eclipse.persistence.jpa.embeddable.model.SpecPhone;
 import org.eclipse.persistence.jpa.test.framework.DDLGen;
 import org.eclipse.persistence.jpa.test.framework.Emf;
 import org.eclipse.persistence.jpa.test.framework.EmfRunner;
 import org.eclipse.persistence.jpa.test.framework.Property;
+import org.eclipse.persistence.jpa.test.framework.SQLListener;
+import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -35,6 +43,13 @@
                     @Property(name = "eclipselink.cache.shared.default", value = "false")})
     private EntityManagerFactory emf;
 
+    @Emf(name = "SpecPersistenceUnit", createTables = DDLGen.DROP_CREATE, 
+            classes = { SpecAddress.class, SpecContactInfo.class, SpecEmployee.class, SpecPhone.class })
+    private EntityManagerFactory emf2;
+
+    @SQLListener(name = "SpecPersistenceUnit")
+    List<String> _sql;
+
     @Test
     public void mergeTest() {
         if (emf == null)
@@ -64,4 +79,23 @@
             em.close();
         }
     }
+
+    @Test
+    public void JPQLAggregateCollectionTests() {
+        EntityManager em = emf2.createEntityManager();
+        try {
+            /*
+             * ContactInfo.previousAddresses should end up being an AggregateCollectionMapping
+             */
+            Query queryEmbed = em.createQuery("SELECT p.city FROM SpecEmployee e JOIN e.contactInfo.previousAddresses p WHERE e.contactInfo.primaryAddress.zipcode = ?1");
+            queryEmbed.setParameter(1, "95054");
+            queryEmbed.getResultList();
+            Assert.assertEquals(1, _sql.size());
+            Assert.assertEquals("SELECT t0.CITY FROM PREV_ADDRESSES t0, SPECEMPLOYEE t1 WHERE ((t1.ZIPCODE = ?) AND (t0.SpecEmployee_ID = t1.ID))", _sql.remove(0));
+        } finally {
+            if (em.isOpen()) {
+                em.close();
+            }
+        }
+    }
 }
diff --git a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/TestEmbeddable.java b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/TestEmbeddable.java
new file mode 100644
index 0000000..2d8fc5b
--- /dev/null
+++ b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/TestEmbeddable.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2021 IBM Corporation. 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:
+//     04/09/2021 - Will Dazey
+//       - 570702 : Using embeddable fields in query JOINs
+package org.eclipse.persistence.jpa.embeddable;
+
+import java.util.List;
+
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.EntityManagerFactory;
+import jakarta.persistence.Query;
+
+import org.eclipse.persistence.jpa.embeddable.model.SpecAddress;
+import org.eclipse.persistence.jpa.embeddable.model.SpecContactInfo;
+import org.eclipse.persistence.jpa.embeddable.model.SpecEmployee;
+import org.eclipse.persistence.jpa.embeddable.model.SpecPhone;
+import org.eclipse.persistence.jpa.test.framework.DDLGen;
+import org.eclipse.persistence.jpa.test.framework.Emf;
+import org.eclipse.persistence.jpa.test.framework.EmfRunner;
+import org.eclipse.persistence.jpa.test.framework.SQLListener;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(EmfRunner.class)
+public class TestEmbeddable {
+
+    @Emf(name = "SpecPersistenceUnit", createTables = DDLGen.DROP_CREATE, 
+            classes = { SpecAddress.class, SpecContactInfo.class, SpecEmployee.class, SpecPhone.class })
+    private EntityManagerFactory emf;
+
+    @SQLListener(name = "SpecPersistenceUnit")
+    List<String> _sql;
+
+    @Test
+    public void JPQLTests() {
+        EntityManager em = emf.createEntityManager();
+        try {
+            // According to the JPA Spec, section 4.4.4, the following two queries are equivalent
+            Query queryEmbed = em.createQuery("SELECT p.vendor FROM SpecEmployee e JOIN e.contactInfo.phones p WHERE e.contactInfo.primaryAddress.zipcode = ?1");
+            queryEmbed.setParameter(1, "95051");
+            queryEmbed.getResultList();
+            Assert.assertEquals(1, _sql.size());
+            Assert.assertEquals("SELECT t0.VENDOR FROM SPECPHONE t0, SPECEMPLOYEE_SPECPHONE t2, SPECEMPLOYEE t1 WHERE ((t1.ZIPCODE = ?) AND ((t2.SpecEmployee_ID = t1.ID) AND (t0.ID = t2.phones_ID)))", _sql.remove(0));
+
+            queryEmbed = em.createQuery("SELECT p.vendor FROM SpecEmployee e JOIN e.contactInfo c JOIN c.phones p WHERE e.contactInfo.primaryAddress.zipcode = ?1");
+            queryEmbed.setParameter(1, "95052");
+            queryEmbed.getResultList();
+            Assert.assertEquals(1, _sql.size());
+            Assert.assertEquals("SELECT t0.VENDOR FROM SPECPHONE t0, SPECEMPLOYEE_SPECPHONE t2, SPECEMPLOYEE t1 WHERE ((t1.ZIPCODE = ?) AND ((t2.SpecEmployee_ID = t1.ID) AND (t0.ID = t2.phones_ID)))", _sql.remove(0));
+        } finally {
+            if (em.isOpen()) {
+                em.close();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/TestNestedEmbeddable.java b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/TestNestedEmbeddable.java
index dd01243..102c40f 100644
--- a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/TestNestedEmbeddable.java
+++ b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/TestNestedEmbeddable.java
@@ -1,6 +1,6 @@
 /*
- * Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2016 IBM Corporation. All rights reserved.
+ * Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2021 IBM Corporation. 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
@@ -20,6 +20,9 @@
 
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.EntityManagerFactory;
+import jakarta.persistence.Query;
+
+import java.util.List;
 
 import org.eclipse.persistence.descriptors.ClassDescriptor;
 import org.eclipse.persistence.jpa.JpaEntityManager;
@@ -28,10 +31,15 @@
 import org.eclipse.persistence.jpa.embeddable.model.DeepOrder;
 import org.eclipse.persistence.jpa.embeddable.model.Order;
 import org.eclipse.persistence.jpa.embeddable.model.OrderPK;
+import org.eclipse.persistence.jpa.embeddable.model.SpecAddress;
+import org.eclipse.persistence.jpa.embeddable.model.SpecContactInfo;
+import org.eclipse.persistence.jpa.embeddable.model.SpecEmployee;
+import org.eclipse.persistence.jpa.embeddable.model.SpecPhone;
 import org.eclipse.persistence.jpa.embeddable.model.Zipcode;
 import org.eclipse.persistence.jpa.test.framework.DDLGen;
 import org.eclipse.persistence.jpa.test.framework.Emf;
 import org.eclipse.persistence.jpa.test.framework.EmfRunner;
+import org.eclipse.persistence.jpa.test.framework.SQLListener;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -40,7 +48,14 @@
 public class TestNestedEmbeddable {
     @Emf(createTables = DDLGen.DROP_CREATE, classes = { DeepOrder.class, DeepOrderPK.class, Order.class, OrderPK.class, Address.class, Zipcode.class })
         private EntityManagerFactory emf;
-    
+
+    @Emf(name = "SpecPersistenceUnit", createTables = DDLGen.DROP_CREATE, 
+            classes = { SpecAddress.class, SpecContactInfo.class, SpecEmployee.class, SpecPhone.class })
+    private EntityManagerFactory emf2;
+
+    @SQLListener(name = "SpecPersistenceUnit")
+    List<String> _sql;
+
     @Test
     public void persistTest() {
         if (emf == null)
@@ -109,4 +124,27 @@
             em.close();
         }
     }
+
+    @Test
+    public void JPQLNestedEmbeddableTests() {
+        EntityManager em = emf2.createEntityManager();
+        try {
+            // Test to make sure that moving around the dot notation for nested embeddables is valid and doesn't change the query
+            Query queryEmbed = em.createQuery("SELECT c.primaryAddress.city FROM SpecEmployee e JOIN e.contactInfo c WHERE e.contactInfo.primaryAddress.zipcode = ?1");
+            queryEmbed.setParameter(1, "95053");
+            queryEmbed.getResultList();
+            Assert.assertEquals(1, _sql.size());
+            Assert.assertEquals("SELECT CITY FROM SPECEMPLOYEE WHERE (ZIPCODE = ?)", _sql.remove(0));
+
+            queryEmbed = em.createQuery("SELECT p.city FROM SpecEmployee e JOIN e.contactInfo.primaryAddress p WHERE e.contactInfo.primaryAddress.zipcode = ?1");
+            queryEmbed.setParameter(1, "95054");
+            queryEmbed.getResultList();
+            Assert.assertEquals(1, _sql.size());
+            Assert.assertEquals("SELECT CITY FROM SPECEMPLOYEE WHERE (ZIPCODE = ?)", _sql.remove(0));
+        } finally {
+            if (em.isOpen()) {
+                em.close();
+            }
+        }
+    }
 }
diff --git a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/model/SpecAddress.java b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/model/SpecAddress.java
new file mode 100644
index 0000000..a477bf4
--- /dev/null
+++ b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/model/SpecAddress.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2021 IBM Corporation. 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:
+//     04/09/2021 - Will Dazey
+//       - 570702 : Using embeddable fields in query JOINs
+package org.eclipse.persistence.jpa.embeddable.model;
+
+import jakarta.persistence.Embeddable;
+
+@Embeddable
+public class SpecAddress {
+    private String street;
+    private String city;
+    private String state;
+    private String zipcode;
+} 
\ No newline at end of file
diff --git a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/model/SpecContactInfo.java b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/model/SpecContactInfo.java
new file mode 100644
index 0000000..c708d8b
--- /dev/null
+++ b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/model/SpecContactInfo.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2021 IBM Corporation. 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:
+//     04/09/2021 - Will Dazey
+//       - 570702 : Using embeddable fields in query JOINs
+package org.eclipse.persistence.jpa.embeddable.model;
+
+import java.util.List;
+
+import jakarta.persistence.CollectionTable;
+import jakarta.persistence.ElementCollection;
+import jakarta.persistence.Embeddable;
+import jakarta.persistence.Embedded;
+import jakarta.persistence.ManyToMany;
+
+@Embeddable
+public class SpecContactInfo {
+    @Embedded
+    private SpecAddress primaryAddress;
+
+    @ElementCollection
+    @CollectionTable(name="PREV_ADDRESSES")
+    private List<SpecAddress> previousAddresses;
+
+    @ManyToMany 
+    private List<SpecPhone> phones;
+} 
\ No newline at end of file
diff --git a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/model/SpecEmployee.java b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/model/SpecEmployee.java
new file mode 100644
index 0000000..fd333ce
--- /dev/null
+++ b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/model/SpecEmployee.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2021 IBM Corporation. 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:
+//     04/09/2021 - Will Dazey
+//       - 570702 : Using embeddable fields in query JOINs
+package org.eclipse.persistence.jpa.embeddable.model;
+
+import jakarta.persistence.Embedded;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+
+@Entity
+public class SpecEmployee {
+    @Id
+    private int id;
+
+    @Embedded
+    private SpecContactInfo contactInfo;
+} 
\ No newline at end of file
diff --git a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/model/SpecPhone.java b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/model/SpecPhone.java
new file mode 100644
index 0000000..f8f43a3
--- /dev/null
+++ b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/embeddable/model/SpecPhone.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2021 IBM Corporation. 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:
+//     04/09/2021 - Will Dazey
+//       - 570702 : Using embeddable fields in query JOINs
+package org.eclipse.persistence.jpa.embeddable.model;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+
+@Entity
+public class SpecPhone {
+    @Id 
+    private int id;
+
+    private String vendor;
+} 
\ No newline at end of file
diff --git a/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/jpql/JUnitJPQLValidationTestSuite.java b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/jpql/JUnitJPQLValidationTestSuite.java
index 9d80de0..ad2323e 100644
--- a/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/jpql/JUnitJPQLValidationTestSuite.java
+++ b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/jpql/JUnitJPQLValidationTestSuite.java
@@ -129,7 +129,7 @@
         suite.addTest(new JUnitJPQLValidationTestSuite("testParameterPositionValidation2"));
         suite.addTest(new JUnitJPQLValidationTestSuite("testParameterTypeValidation"));
         suite.addTest(new JUnitJPQLValidationTestSuite("testEjbqlCaseSensitivity"));
-        suite.addTest(new JUnitJPQLValidationTestSuite("testEjbqlUnsupportJoinArgument"));
+        suite.addTest(new JUnitJPQLValidationTestSuite("testEjbqlSupportJoinArgument"));
         suite.addTest(new JUnitJPQLValidationTestSuite("testInvalidSetClause"));
         suite.addTest(new JUnitJPQLValidationTestSuite("testUnsupportedCountDistinctOnOuterJoinedCompositePK"));
         suite.addTest(new JUnitJPQLValidationTestSuite("testInvalidHint"));
@@ -1181,29 +1181,27 @@
         }
     }
 
-    public void testEjbqlUnsupportJoinArgument() {
+    public void testEjbqlSupportJoinArgument() {
+        boolean testPass = true;
         String ejbqlString;
-        List result;
 
         try
         {
             ejbqlString = "SELECT e.firstName FROM Employee e JOIN e.period ep";
-            result = createEntityManager().createQuery(ejbqlString).getResultList();
-            fail ("JOINing of embedded entities is not allowed must be thrown");
-        } catch(IllegalArgumentException ex)
-        {
-            Assert.assertTrue(ex.getCause() instanceof JPQLException);
+            createEntityManager().createQuery(ejbqlString).getResultList();
+        } catch(Exception ex) {
+            testPass = false;
         }
+        Assert.assertTrue(testPass);
 
         try
         {
             ejbqlString = "SELECT e.firstName FROM Employee e JOIN FETCH e.period";
-            result = createEntityManager().createQuery(ejbqlString).getResultList();
-            fail ("JOINing of embedded entities is not allowed must be thrown");
-        } catch(IllegalArgumentException ex)
-        {
-            Assert.assertTrue(ex.getCause() instanceof JPQLException);
+            createEntityManager().createQuery(ejbqlString).getResultList();
+        } catch(Exception ex) {
+            testPass = false;
         }
+        Assert.assertTrue(testPass);
     }
 
     public void testInvalidSetClause() {
diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/AbstractSemanticValidator.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/AbstractSemanticValidator.java
index 699e8b8..f8a2634 100644
--- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/AbstractSemanticValidator.java
+++ b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/AbstractSemanticValidator.java
@@ -1,5 +1,6 @@
 /*
- * Copyright (c) 2006, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021 IBM Corporation. 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
@@ -939,6 +940,76 @@
     }
 
     /**
+     * Validates the given {@link Expression} and makes sure it's a valid collection value path expression.
+     * 
+     * join_collection_valued_path_expression::=
+     *     identification_variable.{single_valued_embeddable_object_field.}*collection_valued_field
+     * join_single_valued_path_expression::=
+     *     identification_variable.{single_valued_embeddable_object_field.}*single_valued_object_field
+     *
+     * @param expression The {@link Expression} to validate
+     * @param collectionTypeOnly <code>true</code> to make sure the path expression resolves to a
+     * collection mapping only; <code>false</code> if it can simply resolves to a relationship mapping
+     */
+    protected boolean validateJoinCollectionValuedPathExpression(Expression expression,
+                                                             boolean collectionTypeOnly) {
+
+        boolean valid = true;
+
+        // The path expression resolves to a collection-valued path expression
+        CollectionValuedPathExpression collectionValuedPathExpression = getCollectionValuedPathExpression(expression);
+
+        if (collectionValuedPathExpression != null &&
+            collectionValuedPathExpression.hasIdentificationVariable() &&
+           !collectionValuedPathExpression.endsWithDot()) {
+
+            // A collection_valued_field is designated by the name of an association field in a
+            // one-to-many or a many-to-many relationship or by the name of an element collection field
+
+            // A single_valued_object_field is designated by the name of an association field in a one-to-one or
+            // many-to-one relationship or a field of embeddable class type
+            Object mapping = helper.resolveMapping(expression);
+            Object type = helper.getMappingType(mapping);
+
+            // Does not resolve to a valid path
+            if (!helper.isTypeResolvable(type) || (mapping == null)) {
+
+                int startPosition = position(expression);
+                int endPosition   = startPosition + length(expression);
+
+                addProblem(
+                    expression,
+                    startPosition,
+                    endPosition,
+                    CollectionValuedPathExpression_NotResolvable,
+                    expression.toParsedText()
+                );
+
+                valid = false;
+            }
+            else if (!helper.isCollectionMapping(mapping) && 
+                    !helper.isRelationshipMapping(mapping) && 
+                    !helper.isEmbeddableMapping(mapping)) {
+
+                int startPosition = position(expression);
+                int endPosition   = startPosition + length(expression);
+
+                addProblem(
+                    expression,
+                    startPosition,
+                    endPosition,
+                    CollectionValuedPathExpression_NotCollectionType,
+                    expression.toParsedText()
+                );
+
+                valid = false;
+            }
+        }
+
+        return valid;
+    }
+
+    /**
      * Validates the left and right expressions of the given {@link ComparisonExpression}. The tests
      * to perform are:
      * <ul>
@@ -1605,7 +1676,7 @@
 
         if (expression.hasJoinAssociationPath()) {
             Expression joinAssociationPath = expression.getJoinAssociationPath();
-            validateCollectionValuedPathExpression(joinAssociationPath, false);
+            validateJoinCollectionValuedPathExpression(joinAssociationPath, false);
             joinAssociationPath.accept(this);
         }
 
diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/SemanticValidatorHelper.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/SemanticValidatorHelper.java
index 4873e94..e428afa 100644
--- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/SemanticValidatorHelper.java
+++ b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/SemanticValidatorHelper.java
@@ -1,5 +1,6 @@
 /*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021 IBM Corporation. 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
@@ -293,6 +294,18 @@
     boolean isCollectionMapping(Object mapping);
 
     /**
+     * Determines whether the given mapping is an embeddable type mapping.
+     * <p>
+     * If it was going through Hermes SPI, the type of the arguments would be
+     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IMapping IMapping}.
+     *
+     * @param mapping The mapping object to verify if it represents an embeddable mapping
+     * @return <code>true</code> if the given mapping is an embeddable mapping; <code>false</code>
+     * otherwise
+     */
+    boolean isEmbeddableMapping(Object mapping);
+
+    /**
      * Determines whether the given type represents an {@link Enum}.
      * <p>
      * If it was going through Hermes SPI, the type of the argument would be
diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/tools/GenericSemanticValidatorHelper.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/tools/GenericSemanticValidatorHelper.java
index 77ada0b..956fe8d 100644
--- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/tools/GenericSemanticValidatorHelper.java
+++ b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/tools/GenericSemanticValidatorHelper.java
@@ -1,5 +1,6 @@
 /*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021 IBM Corporation. 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
@@ -388,6 +389,11 @@
         return (mapping != null) && ((IMapping) mapping).isCollection();
     }
 
+    @Override
+    public boolean isEmbeddableMapping(Object mapping) {
+        return (mapping != null) && ((IMapping) mapping).isEmbeddable();
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/tools/resolver/FromSubqueryResolver.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/tools/resolver/FromSubqueryResolver.java
index cced0d2..2b43c78 100644
--- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/tools/resolver/FromSubqueryResolver.java
+++ b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/tools/resolver/FromSubqueryResolver.java
@@ -1,5 +1,6 @@
 /*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021 IBM Corporation. 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
@@ -305,6 +306,12 @@
             return (mapping != null) ? mapping.isCollection() : false;
         }
 
+        @Override
+        public boolean isEmbeddable() {
+            IMapping mapping = resolver.getMapping();
+            return (mapping != null) ? mapping.isEmbeddable() : false;
+        }
+
         /**
          * {@inheritDoc}
          */
diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/tools/spi/IMapping.java b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/tools/spi/IMapping.java
index 6bc3490..855bbc5 100644
--- a/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/tools/spi/IMapping.java
+++ b/jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/tools/spi/IMapping.java
@@ -1,5 +1,6 @@
 /*
- * Copyright (c) 2006, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021 IBM Corporation. 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
@@ -103,6 +104,15 @@
     boolean isCollection();
 
     /**
+     * Determines whether this {@link IMapping} is an embeddable type mapping.
+     *
+     * @return <code>true</code> if this {@link IMapping} is an embeddable mapping;
+     * <code>false</code> otherwise
+     * @since 2.4
+     */
+    boolean isEmbeddable();
+
+    /**
      * Determines whether this {@link IMapping} is a property type mapping.
      *
      * @return <code>true</code> if this {@link IMapping} is a property mapping; <code>false</code>
diff --git a/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/org/eclipse/persistence/jpa/tests/jpql/tools/spi/java/AbstractMapping.java b/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/org/eclipse/persistence/jpa/tests/jpql/tools/spi/java/AbstractMapping.java
index 1247d7d..af6945d 100644
--- a/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/org/eclipse/persistence/jpa/tests/jpql/tools/spi/java/AbstractMapping.java
+++ b/jpa/org.eclipse.persistence.jpa.jpql/src/test/java/org/eclipse/persistence/jpa/tests/jpql/tools/spi/java/AbstractMapping.java
@@ -1,5 +1,6 @@
 /*
- * Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021 IBM Corporation. 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
@@ -285,6 +286,15 @@
         }
     }
 
+    @Override
+    public boolean isEmbeddable() {
+        switch (getMappingType()) {
+            case EMBEDDED:
+            case EMBEDDED_ID: return true;
+            default:          return false;
+        }
+    }
+
     /**
      * {@inheritDoc}
      */