diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/criteria/path/SingularAttributeJoin.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/criteria/path/SingularAttributeJoin.java index 116ec2c8d5..06f6064b4f 100755 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/criteria/path/SingularAttributeJoin.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/criteria/path/SingularAttributeJoin.java @@ -9,7 +9,10 @@ package org.hibernate.jpa.criteria.path; import javax.persistence.criteria.JoinType; import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.Bindable; +import javax.persistence.metamodel.ManagedType; +import javax.persistence.metamodel.PluralAttribute; import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.Type; import org.hibernate.jpa.criteria.CriteriaBuilderImpl; import org.hibernate.jpa.criteria.CriteriaSubqueryImpl; @@ -36,13 +39,17 @@ public class SingularAttributeJoin extends AbstractJoinImpl { SingularAttribute joinAttribute, JoinType joinType) { super( criteriaBuilder, javaType, pathSource, joinAttribute, joinType ); - this.model = (Bindable) ( - Attribute.PersistentAttributeType.EMBEDDED == joinAttribute.getPersistentAttributeType() - ? joinAttribute - : javaType != null - ? criteriaBuilder.getEntityManagerFactory().getMetamodel().managedType( javaType ) - : joinAttribute.getType() - ); + if ( Attribute.PersistentAttributeType.EMBEDDED == joinAttribute.getPersistentAttributeType() ) { + this.model = (Bindable) joinAttribute; + } + else { + if ( javaType != null ) { + this.model = (Bindable) criteriaBuilder.getEntityManagerFactory().getMetamodel().managedType( javaType ); + } + else { + this.model = (Bindable) joinAttribute.getType(); + } + } } @Override @@ -71,6 +78,34 @@ public class SingularAttributeJoin extends AbstractJoinImpl { return true; } + @Override + @SuppressWarnings("unchecked") + protected ManagedType locateManagedType() { + if ( getModel().getBindableType() == Bindable.BindableType.ENTITY_TYPE ) { + return (ManagedType) getModel(); + } + else if ( getModel().getBindableType() == Bindable.BindableType.SINGULAR_ATTRIBUTE ) { + final Type joinedAttributeType = ( (SingularAttribute) getAttribute() ).getType(); + if ( !ManagedType.class.isInstance( joinedAttributeType ) ) { + throw new UnsupportedOperationException( + "Cannot further dereference attribute join [" + getPathIdentifier() + "] as its type is not a ManagedType" + ); + } + return (ManagedType) joinedAttributeType; + } + else if ( getModel().getBindableType() == Bindable.BindableType.PLURAL_ATTRIBUTE ) { + final Type elementType = ( (PluralAttribute) getAttribute() ).getElementType(); + if ( !ManagedType.class.isInstance( elementType ) ) { + throw new UnsupportedOperationException( + "Cannot further dereference attribute join [" + getPathIdentifier() + "] (plural) as its element type is not a ManagedType" + ); + } + return (ManagedType) elementType; + } + + return super.locateManagedType(); + } + public Bindable getModel() { return model; } diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/criteria/components/joins/ComponentJoinTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/criteria/components/joins/ComponentJoinTest.java new file mode 100755 index 0000000000..17b82d9b75 --- /dev/null +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/criteria/components/joins/ComponentJoinTest.java @@ -0,0 +1,114 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria.components.joins; + +import java.util.List; +import javax.persistence.EntityManager; +import javax.persistence.Tuple; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Root; +import javax.persistence.metamodel.SingularAttribute; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ComponentJoinTest extends BaseEntityManagerFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Entity.class, EmbeddedType.class, ManyToOneType.class }; + } + + public static final String THEVALUE = "thevalue"; + + @Before + public void before() { + EntityManager entityManager = entityManagerFactory().createEntityManager(); + entityManager.getTransaction().begin(); + ManyToOneType manyToOneType = new ManyToOneType( THEVALUE ); + EmbeddedType embeddedType = new EmbeddedType( manyToOneType ); + Entity entity = new Entity( embeddedType ); + entityManager.persist( entity ); + entityManager.getTransaction().commit(); + entityManager.close(); + } + + @After + public void after() { + EntityManager entityManager = entityManagerFactory().createEntityManager(); + entityManager.getTransaction().begin(); + entityManager.createQuery( "delete Entity" ).executeUpdate(); + entityManager.createQuery( "delete ManyToOneType" ).executeUpdate(); + entityManager.getTransaction().commit(); + entityManager.close(); + } + + interface JoinBuilder { + Join buildJoinToManyToOneType(Join source); + } + + private void doTest(JoinBuilder joinBuilder) { + EntityManager entityManager = getOrCreateEntityManager(); + entityManager.getTransaction().begin(); + + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = builder.createTupleQuery(); + Root root = criteriaQuery.from( Entity.class ); + Join join = root.join( "embeddedType", JoinType.LEFT ); + + // left join to the manyToOne on the embeddable with a string property + Path path = joinBuilder.buildJoinToManyToOneType( join ).get( "value" ); + + // select the path in the tuple + criteriaQuery.select( builder.tuple( path ) ); + + List results = entityManager.createQuery( criteriaQuery ).getResultList(); + Tuple result = results.iterator().next(); + + assertEquals( THEVALUE, result.get(0) ); + + entityManager.getTransaction().commit(); + entityManager.close(); + } + + @Test + public void getResultWithStringPropertyDerivedPath() { + doTest( + new JoinBuilder() { + @Override + public Join buildJoinToManyToOneType(Join source) { + return source.join( "manyToOneType", JoinType.LEFT ); + } + } + ); + } + + @Test + @SuppressWarnings("unchecked") + public void getResultWithMetamodelDerivedPath() { + doTest( + new JoinBuilder() { + @Override + public Join buildJoinToManyToOneType(Join source) { + final SingularAttribute attr = + (SingularAttribute) entityManagerFactory().getMetamodel() + .managedType( EmbeddedType.class ) + .getDeclaredSingularAttribute( "manyToOneType" ); + return source.join( attr, JoinType.LEFT ); + } + } + ); + } +} diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/criteria/components/joins/EmbeddedType.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/criteria/components/joins/EmbeddedType.java new file mode 100755 index 0000000000..302539beb6 --- /dev/null +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/criteria/components/joins/EmbeddedType.java @@ -0,0 +1,36 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ + +package org.hibernate.jpa.test.criteria.components.joins; + +import javax.persistence.CascadeType; +import javax.persistence.Embeddable; +import javax.persistence.ManyToOne; + +/** + * @author Matt Todd + */ +@Embeddable +public class EmbeddedType { + @ManyToOne(cascade = CascadeType.ALL) + private ManyToOneType manyToOneType; + + public EmbeddedType() { + } + + public EmbeddedType(ManyToOneType manyToOneType) { + this.manyToOneType = manyToOneType; + } + + public ManyToOneType getManyToOneType() { + return manyToOneType; + } + + public void setManyToOneType(ManyToOneType manyToOneType) { + this.manyToOneType = manyToOneType; + } +} diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/criteria/components/joins/Entity.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/criteria/components/joins/Entity.java new file mode 100755 index 0000000000..60b8e79ec1 --- /dev/null +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/criteria/components/joins/Entity.java @@ -0,0 +1,37 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria.components.joins; + +import javax.persistence.Embedded; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +@javax.persistence.Entity +public class Entity { + @Id + @GeneratedValue + private Long id; + + @Embedded + private EmbeddedType embeddedType; + + public Entity() { + // for jpa + } + + public Entity(EmbeddedType embeddedType) { + this.embeddedType = embeddedType; + } + + public EmbeddedType getEmbeddedType() { + return embeddedType; + } + + public void setEmbeddedType(EmbeddedType embeddedType) { + this.embeddedType = embeddedType; + } +} diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/criteria/components/joins/ManyToOneType.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/criteria/components/joins/ManyToOneType.java new file mode 100755 index 0000000000..a902ad752c --- /dev/null +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/criteria/components/joins/ManyToOneType.java @@ -0,0 +1,37 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria.components.joins; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +/** + * @author Matt Todd + */ +@Entity +public class ManyToOneType { + @Id + @GeneratedValue + public Long id; + private String value; + + public ManyToOneType() { + } + + public ManyToOneType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +}