diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/PluralAttributePath.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/PluralAttributePath.java index a89de72b14..7b0fc3e332 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/PluralAttributePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/PluralAttributePath.java @@ -11,9 +11,12 @@ import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.Bindable; import javax.persistence.metamodel.EntityType; import javax.persistence.metamodel.IdentifiableType; +import javax.persistence.metamodel.MappedSuperclassType; import javax.persistence.metamodel.PluralAttribute; +import javax.persistence.metamodel.SingularAttribute; import javax.persistence.metamodel.Type; +import org.hibernate.AssertionFailure; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; import org.hibernate.query.criteria.internal.PathSource; @@ -44,39 +47,73 @@ public class PluralAttributePath extends AbstractPathImpl implements Seria } private String resolveRole(PluralAttribute attribute) { - Class roleOwnerType = attribute.getDeclaringType().getJavaType(); - if ( attribute.getDeclaringType().getPersistenceType() == Type.PersistenceType.MAPPED_SUPERCLASS ) { - // the attribute is declared in a mappedsuperclass - if ( getPathSource().getModel().getBindableType() == Bindable.BindableType.ENTITY_TYPE ) { - // the role will be assigned to the "nearest" EnityType subclass of the MappedSuperclassType - EntityType entityTypeNearestDeclaringType = (EntityType) getPathSource().getModel(); - IdentifiableType superType = entityTypeNearestDeclaringType.getSupertype(); - IdentifiableType previousType = entityTypeNearestDeclaringType; - while ( superType != attribute.getDeclaringType() ) { - if ( superType == null ) { - throw new IllegalStateException( - String.format( - "Cannot determine nearest EntityType extending mapped superclass [%s]; [%s] extends [%s], but supertype of [%s] is null", - attribute.getDeclaringType().getJavaType().getName(), - ( (EntityType) getPathSource().getModel() ).getJavaType().getName(), - previousType.getJavaType().getName(), - previousType.getJavaType().getName() - ) - ); - } - if ( superType.getPersistenceType() == Type.PersistenceType.ENTITY ) { - entityTypeNearestDeclaringType = (EntityType) superType; - } - previousType = superType; - superType = superType.getSupertype(); - } - roleOwnerType = entityTypeNearestDeclaringType.getJavaType(); + switch ( attribute.getDeclaringType().getPersistenceType() ) { + case ENTITY: { + return attribute.getDeclaringType().getJavaType().getName() + '.' + attribute.getName(); } - // else throw an exception? + case MAPPED_SUPERCLASS: { + // the attribute is declared in a mappedsuperclass + if ( getPathSource().getModel().getBindableType() == Bindable.BindableType.ENTITY_TYPE ) { + // the role will be assigned to the "nearest" EnityType subclass of the MappedSuperclassType + final EntityType entityTypeNearestDeclaringType = locateNearestSubclassEntity( + (MappedSuperclassType) attribute.getDeclaringType(), + (EntityType) getPathSource().getModel() + ); + return entityTypeNearestDeclaringType.getJavaType().getName() + '.' + attribute.getName(); + } + else { + throw new AssertionFailure( + String.format( + "Unexpected BindableType; expected [%s]; instead got [%s]", + Bindable.BindableType.ENTITY_TYPE, + getPathSource().getModel().getBindableType() + ) + ); + } + } + case EMBEDDABLE: { + // initialize role to '.' + + StringBuilder role = new StringBuilder().append( '.' ).append( attribute.getName() ); + PathSource parentPath = getPathSource(); + SingularAttribute singularAttribute; + do { + final SingularAttributePath singularAttributePath = (SingularAttributePath) parentPath; + singularAttribute = singularAttributePath.getAttribute(); + // insert '.' + at start of role + role.insert( 0, '.' ); + role.insert( 1, singularAttributePath.getAttribute().getName() ); + parentPath = singularAttributePath.getPathSource(); + } while ( ( SingularAttributePath.class.isInstance( parentPath ) ) ); + final EntityType entityType; + if ( singularAttribute.getDeclaringType().getPersistenceType() == Type.PersistenceType.ENTITY ) { + entityType = (EntityType) singularAttribute.getDeclaringType(); + } + else if ( singularAttribute.getDeclaringType().getPersistenceType() == Type.PersistenceType.MAPPED_SUPERCLASS ){ + // find the "nearest" EnityType subclass of the MappedSuperclassType + entityType = locateNearestSubclassEntity( + (MappedSuperclassType) singularAttribute.getDeclaringType(), + (EntityType) parentPath.getModel() + ); + } + else { + throw new AssertionFailure( + String.format( + "Unexpected PersistenceType: [%s]", + singularAttribute.getDeclaringType().getPersistenceType() + ) + ); + } + // insert at start of role + return role.insert( 0, entityType.getJavaType().getName() ).toString(); + } + default: + throw new AssertionFailure( + String.format( + "Unexpected PersistenceType: [%s]", + attribute.getDeclaringType().getPersistenceType() + ) + ); } - // TODO: still need to deal with a plural attribute declared in an embeddable (HHH-6562) - return roleOwnerType.getName() + - '.' + attribute.getName(); } public PluralAttribute getAttribute() { @@ -115,4 +152,26 @@ public class PluralAttributePath extends AbstractPathImpl implements Seria + attribute.getName() + "] cannot be dereferenced" ); } + + private EntityType locateNearestSubclassEntity(MappedSuperclassType mappedSuperclassType, EntityType entityTypeTop) { + EntityType entityTypeNearestDeclaringType = entityTypeTop; + IdentifiableType superType = entityTypeNearestDeclaringType.getSupertype(); + while ( superType != mappedSuperclassType ) { + if ( superType == null ) { + throw new IllegalStateException( + String.format( + "Cannot determine nearest EntityType extending mapped superclass [%s] starting from [%s]; a supertype of [%s] is null", + mappedSuperclassType.getJavaType().getName(), + entityTypeTop.getJavaType().getName(), + entityTypeTop.getJavaType().getName() + ) + ); + } + if ( superType.getPersistenceType() == Type.PersistenceType.ENTITY ) { + entityTypeNearestDeclaringType = (EntityType) superType; + } + superType = superType.getSupertype(); + } + return entityTypeNearestDeclaringType; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/ComponentInWhereClauseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/ComponentInWhereClauseTest.java new file mode 100644 index 0000000000..289d53f7a0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/ComponentInWhereClauseTest.java @@ -0,0 +1,351 @@ +/* + * 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; + +import javax.persistence.CascadeType; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Before; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.transaction.TransactionUtil; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-6562") +public class ComponentInWhereClauseTest extends BaseEntityManagerFunctionalTestCase { + private Projects projects; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {Employee.class, Project.class, Person.class}; + } + + @Before + public void setUp() throws Exception { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + projects = new Projects(); + projects.addPreviousProject( new Project( "First" ) ); + projects.addPreviousProject( new Project( "Second" ) ); + projects.setCurrentProject( new Project( "Third" ) ); + + ContactDetail contactDetail = new ContactDetail(); + contactDetail.setEmail( "abc@mail.org" ); + contactDetail.addPhone( new Phone( "+4411111111" ) ); + + final Employee employee = new Employee(); + employee.setProjects( projects ); + employee.setContactDetail( contactDetail ); + entityManager.persist( employee ); + + final Person person = new Person(); + person.setInformation( new Information() ); + ContactDetail infoContactDetail = new ContactDetail(); + infoContactDetail.setEmail( "xyz@mail.org" ); + infoContactDetail.addPhone( new Phone( "999-999-9999" ) ); + person.getInformation().setInfoContactDetail( infoContactDetail ); + entityManager.persist( person ); + } + ); + } + + @Test + public void testSizeExpressionForTheOneToManyPropertyOfAComponent() { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Employee.class ); + Root root = query.from( Employee.class ); + + query.where( + builder.equal( + builder.size( root.get( "projects" ).get( "previousProjects" ) ) + , 2 ) ); + + final List results = entityManager.createQuery( query ).getResultList(); + assertThat( results.size(), is( 1 ) ); + } + ); + } + + @Test + public void testSizeExpressionForTheElementCollectionPropertyOfAComponent() { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Employee.class ); + Root root = query.from( Employee.class ); + + query.where( + builder.equal( + builder.size( root.get( "contactDetail" ).get( "phones" ) ) + , 1 ) + ); + + final List results = entityManager.createQuery( query ).getResultList(); + assertThat( results.size(), is( 1 ) ); + } + ); + } + + @Test + public void testSizeExpressionForTheElementCollectionPropertyOfASubComponent() { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Person.class ); + Root root = query.from( Person.class ); + + query.where( + builder.equal( + builder.size( root.get( "information" ).get( "infoContactDetail" ).get( "phones" ) ) + , 1 ) + ); + + final List results = entityManager.createQuery( query ).getResultList(); + assertThat( results.size(), is( 1 ) ); + } + ); + } + + @Test + public void testEqualExpressionForThePropertyOfTheElementCollectionPropertyOfAComponent() { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Employee.class ); + Root root = query.from( Employee.class ); + + query.where( + builder.equal( + root.join( "contactDetail" ).join( "phones" ).get( "number" ) + , "+4411111111" ) + ); + + final List results = entityManager.createQuery( query ).getResultList(); + assertThat( results.size(), is( 1 ) ); + } + ); + } + + @Test + public void testEqualityForThePropertyOfAComponent() { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Employee.class ); + Root root = query.from( Employee.class ); + + query.where( + builder.equal( + root.join( "contactDetail" ).get( "email" ) + , "abc@mail.org" + ) + ); + + final List results = entityManager.createQuery( query ).getResultList(); + assertThat( results.size(), is( 1 ) ); + } + ); + } + + @Test + public void testInExpressionForAComponent() { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Employee.class ); + Root root = query.from( Employee.class ); + + query.where( root.get( "projects" ).in( projects, new Projects() ) ); + + final List results = entityManager.createQuery( query ).getResultList(); + assertThat( results.size(), is( 1 ) ); + } + ); + } + + @Test + public void testInExpressionForTheManyToOnePropertyOfAComponent() { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Employee.class ); + Root root = query.from( Employee.class ); + + query.where( root.get( "projects" ) + .get( "currentProject" ) + .in( projects.getCurrentProject() ) ); + + final List results = entityManager.createQuery( query ).getResultList(); + assertThat( results.size(), is( 1 ) ); + } + ); + } + + @MappedSuperclass + public static abstract class AbstractEntity { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + protected Long id; + + public Long getId() { + return id; + } + } + + @Entity(name = "Employee") + @Table(name = "EMPLOYEE") + public static class Employee extends AbstractEntity { + + @Embedded + private Projects projects; + + @Embedded + private ContactDetail contactDetail; + + public void setProjects(Projects projects) { + this.projects = projects; + } + + public void setContactDetail(ContactDetail contactDetail) { + this.contactDetail = contactDetail; + } + } + + @Embeddable + public static class ContactDetail { + private String email; + + @ElementCollection + private List phones = new ArrayList<>(); + + public void addPhone(Phone phone) { + this.phones.add( phone ); + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + } + + @Embeddable + public static class Projects { + + @OneToMany(cascade = CascadeType.PERSIST) + private Set previousProjects = new HashSet<>(); + + @ManyToOne(cascade = CascadeType.PERSIST) + private Project currentProject; + + public void setCurrentProject(Project project) { + this.currentProject = project; + } + + public void addPreviousProject(Project project) { + this.previousProjects.add( project ); + } + + public Set getPreviousProjects() { + return previousProjects; + } + + public Project getCurrentProject() { + return currentProject; + } + } + + @Entity(name = "Project") + @Table(name = "PROJECT") + public static class Project extends AbstractEntity { + + public Project() { + } + + public Project(String name) { + this.name = name; + } + + private String name; + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + } + + @Embeddable + public static class Phone { + private String number; + + public Phone() { + } + + public Phone(String number) { + this.number = number; + } + + public String getNumber() { + return this.number; + } + } + + @Entity(name = "Person") + @Table(name="PERSON") + public static class Person extends AbstractEntity { + @Embedded + private Information information; + + public Information getInformation() { + return information; + } + + public void setInformation(Information information) { + this.information = information; + } + } + + @Embeddable + public static class Information { + @Embedded + private ContactDetail infoContactDetail; + + public ContactDetail getInfoContactDetail() { + return infoContactDetail; + } + + public void setInfoContactDetail(ContactDetail infoContactDetail) { + this.infoContactDetail = infoContactDetail; + } + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/EntitySuperclassComponentWithCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/EntitySuperclassComponentWithCollectionTest.java new file mode 100644 index 0000000000..0bfb27607a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/EntitySuperclassComponentWithCollectionTest.java @@ -0,0 +1,289 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.junit.Before; +import org.junit.Test; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.transaction.TransactionUtil; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-6562") +public class EntitySuperclassComponentWithCollectionTest extends BaseEntityManagerFunctionalTestCase { + private Projects projects; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {Employee.class, Manager.class, Project.class, Person.class, Leader.class}; + } + + @Before + public void setUp() throws Exception { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + projects = new Projects(); + projects.addPreviousProject( new Project( "First" ) ); + projects.addPreviousProject( new Project( "Second" ) ); + projects.setCurrentProject( new Project( "Third" ) ); + + ContactDetail contactDetail = new ContactDetail(); + contactDetail.setEmail( "abc@mail.org" ); + contactDetail.addPhone( new Phone( "+4411111111" ) ); + + final Manager manager = new Manager(); + manager.setProjects( projects ); + manager.setContactDetail( contactDetail ); + entityManager.persist( manager ); + + final Leader leader = new Leader(); + leader.setInformation( new Information() ); + ContactDetail infoContactDetail = new ContactDetail(); + infoContactDetail.setEmail( "xyz@mail.org" ); + infoContactDetail.addPhone( new Phone( "999-999-9999" ) ); + leader.getInformation().setInfoContactDetail( infoContactDetail ); + entityManager.persist( leader ); + } + ); + } + + @Test + public void testSizeExpressionForTheOneToManyPropertyOfAComponent() { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Manager.class ); + Root root = query.from( Manager.class ); + + query.where( + builder.equal( + builder.size( root.get( "projects" ).get( "previousProjects" ) ) + , 2 ) ); + + final List results = entityManager.createQuery( query ).getResultList(); + assertThat( results.size(), is( 1 ) ); + } + ); + } + + @Test + public void testSizeExpressionForTheElementCollectionPropertyOfAComponent() { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Manager.class ); + Root root = query.from( Manager.class ); + + query.where( + builder.equal( + builder.size( root.get( "contactDetail" ).get( "phones" ) ) + , 1 ) + ); + + final List results = entityManager.createQuery( query ).getResultList(); + assertThat( results.size(), is( 1 ) ); + } + ); + } + + @Test + public void testSizeExpressionForTheElementCollectionPropertyOfASubComponent() { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Leader.class ); + Root root = query.from( Leader.class ); + + query.where( + builder.equal( + builder.size( root.get( "information" ).get( "infoContactDetail" ).get( "phones" ) ) + , 1 ) + ); + + final List results = entityManager.createQuery( query ).getResultList(); + assertThat( results.size(), is( 1 ) ); + } + ); + } + + @MappedSuperclass + public static abstract class AbstractEntity { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + protected Long id; + + public Long getId() { + return id; + } + } + + @Entity(name = "Employee") + @Table(name = "EMPLOYEE") + public static class Employee extends AbstractEntity { + + @Embedded + private Projects projects; + + @Embedded + private ContactDetail contactDetail; + + public void setProjects(Projects projects) { + this.projects = projects; + } + + public void setContactDetail(ContactDetail contactDetail) { + this.contactDetail = contactDetail; + } + } + + @Entity(name = "Manager") + @Table(name = "MANAGER") + public static class Manager extends Employee { + } + + @Embeddable + public static class ContactDetail { + private String email; + + @ElementCollection + private List phones = new ArrayList<>(); + + public void addPhone(Phone phone) { + this.phones.add( phone ); + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + } + + @Embeddable + public static class Projects { + + @OneToMany(cascade = CascadeType.PERSIST) + private Set previousProjects = new HashSet<>(); + + @ManyToOne(cascade = CascadeType.PERSIST) + private Project currentProject; + + public void setCurrentProject(Project project) { + this.currentProject = project; + } + + public void addPreviousProject(Project project) { + this.previousProjects.add( project ); + } + + public Set getPreviousProjects() { + return previousProjects; + } + + public Project getCurrentProject() { + return currentProject; + } + } + + @Entity(name = "Project") + @Table(name = "PROJECT") + public static class Project extends AbstractEntity { + + public Project() { + } + + public Project(String name) { + this.name = name; + } + + private String name; + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + } + + @Embeddable + public static class Phone { + private String number; + + public Phone() { + } + + public Phone(String number) { + this.number = number; + } + + public String getNumber() { + return this.number; + } + } + + @Entity(name = "Person") + @Table(name="PERSON") + public static class Person extends AbstractEntity { + @Embedded + private Information information; + + public Information getInformation() { + return information; + } + + public void setInformation(Information information) { + this.information = information; + } + } + + @Entity(name="Leader") + @Table(name="LEADER") + public static class Leader extends Person { + } + + @Embeddable + public static class Information { + @Embedded + private ContactDetail infoContactDetail; + + public ContactDetail getInfoContactDetail() { + return infoContactDetail; + } + + public void setInfoContactDetail(ContactDetail infoContactDetail) { + this.infoContactDetail = infoContactDetail; + } + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/MappedSuperclassComponentWithCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/MappedSuperclassComponentWithCollectionTest.java new file mode 100644 index 0000000000..017d9dcd4b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/components/MappedSuperclassComponentWithCollectionTest.java @@ -0,0 +1,295 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.junit.Before; +import org.junit.Test; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.transaction.TransactionUtil; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-6562") +public class MappedSuperclassComponentWithCollectionTest extends BaseEntityManagerFunctionalTestCase { + private Projects projects; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {Employee.class, Manager.class, Project.class, Person.class, Leader.class}; + } + + @Before + public void setUp() throws Exception { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + projects = new Projects(); + projects.addPreviousProject( new Project( "First" ) ); + projects.addPreviousProject( new Project( "Second" ) ); + projects.setCurrentProject( new Project( "Third" ) ); + + ContactDetail contactDetail = new ContactDetail(); + contactDetail.setEmail( "abc@mail.org" ); + contactDetail.addPhone( new Phone( "+4411111111" ) ); + + final Manager manager = new Manager(); + manager.setProjects( projects ); + manager.setContactDetail( contactDetail ); + entityManager.persist( manager ); + + final Leader leader = new Leader(); + leader.setInformation( new Information() ); + ContactDetail infoContactDetail = new ContactDetail(); + infoContactDetail.setEmail( "xyz@mail.org" ); + infoContactDetail.addPhone( new Phone( "999-999-9999" ) ); + leader.getInformation().setInfoContactDetail( infoContactDetail ); + entityManager.persist( leader ); + } + ); + } + + @Test + public void testSizeExpressionForTheOneToManyPropertyOfAComponent() { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Manager.class ); + Root root = query.from( Manager.class ); + + query.where( + builder.equal( + builder.size( root.get( "projects" ).get( "previousProjects" ) ) + , 2 ) ); + + final List results = entityManager.createQuery( query ).getResultList(); + assertThat( results.size(), is( 1 ) ); + } + ); + } + + @Test + public void testSizeExpressionForTheElementCollectionPropertyOfAComponent() { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Manager.class ); + Root root = query.from( Manager.class ); + + query.where( + builder.equal( + builder.size( root.get( "contactDetail" ).get( "phones" ) ) + , 1 ) + ); + + final List results = entityManager.createQuery( query ).getResultList(); + assertThat( results.size(), is( 1 ) ); + } + ); + } + + @Test + public void testSizeExpressionForTheElementCollectionPropertyOfASubComponent() { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Leader.class ); + Root root = query.from( Leader.class ); + + query.where( + builder.equal( + builder.size( root.get( "information" ).get( "infoContactDetail" ).get( "phones" ) ) + , 1 ) + ); + + final List results = entityManager.createQuery( query ).getResultList(); + assertThat( results.size(), is( 1 ) ); + } + ); + } + + @MappedSuperclass + public static abstract class AbstractEntity { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + protected Long id; + + public Long getId() { + return id; + } + } + + @MappedSuperclass + public static class Employee extends AbstractEntity { + + @Embedded + private Projects projects; + + @Embedded + private ContactDetail contactDetail; + + public void setProjects(Projects projects) { + this.projects = projects; + } + + public void setContactDetail(ContactDetail contactDetail) { + this.contactDetail = contactDetail; + } + } + + @Entity(name = "Manager") + @Table(name = "MANAGER") + public static class Manager extends Employee { + } + + @Embeddable + public static class ContactDetail { + private String email; + + @ElementCollection + private List phones = new ArrayList<>(); + + public void addPhone(Phone phone) { + this.phones.add( phone ); + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + } + + @Embeddable + public static class Projects { + + @OneToMany(cascade = CascadeType.PERSIST) + private Set previousProjects = new HashSet<>(); + + @ManyToOne(cascade = CascadeType.PERSIST) + private Project currentProject; + + public void setCurrentProject(Project project) { + this.currentProject = project; + } + + public void addPreviousProject(Project project) { + this.previousProjects.add( project ); + } + + public Set getPreviousProjects() { + return previousProjects; + } + + public Project getCurrentProject() { + return currentProject; + } + } + + @Entity(name = "Project") + @Table(name = "PROJECT") + public static class Project extends AbstractEntity { + + public Project() { + } + + public Project(String name) { + this.name = name; + } + + private String name; + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + } + + @Embeddable + public static class Phone { + private String number; + + public Phone() { + } + + public Phone(String number) { + this.number = number; + } + + public String getNumber() { + return this.number; + } + } + + @MappedSuperclass + public static class Person extends AbstractEntity { + @Embedded + private Information information; + + public Information getInformation() { + return information; + } + + public void setInformation(Information information) { + this.information = information; + } + } + + @Entity + public static class Dummy1 extends Person { + } + + @MappedSuperclass + public static class Dummy2 extends Dummy1 { + } + + @Entity(name="Leader") + @Table(name="LEADER") + public static class Leader extends Person { + } + + @Embeddable + public static class Information { + @Embedded + private ContactDetail infoContactDetail; + + public ContactDetail getInfoContactDetail() { + return infoContactDetail; + } + + public void setInfoContactDetail(ContactDetail infoContactDetail) { + this.infoContactDetail = infoContactDetail; + } + } + + +}