From 5c2657d27c586fe9811c8e1e48642fcfd8eb46a1 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Thu, 20 Apr 2023 14:05:05 +0200 Subject: [PATCH] HHH-16491 Add test for issue --- ...mbeddedIdentifierMappedSuperclassTest.java | 383 +++++++++++++++++ .../GenericEmbeddedIdentifierTest.java | 373 +++++++++++++++++ ...cEmbeddedPropertyMappedSuperclassTest.java | 391 ++++++++++++++++++ .../generics/GenericEmbeddedPropertyTest.java | 381 +++++++++++++++++ 4 files changed, 1528 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/generics/GenericEmbeddedIdentifierMappedSuperclassTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/generics/GenericEmbeddedIdentifierTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/generics/GenericEmbeddedPropertyMappedSuperclassTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/generics/GenericEmbeddedPropertyTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/generics/GenericEmbeddedIdentifierMappedSuperclassTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/generics/GenericEmbeddedIdentifierMappedSuperclassTest.java new file mode 100644 index 0000000000..674fb988c0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/generics/GenericEmbeddedIdentifierMappedSuperclassTest.java @@ -0,0 +1,383 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.annotations.embeddables.generics; + +import java.io.Serializable; + +import org.hibernate.query.criteria.JpaPath; +import org.hibernate.query.sqm.SqmPathSource; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.AssociationOverride; +import jakarta.persistence.AssociationOverrides; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Root; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel( annotatedClasses = { + GenericEmbeddedIdentifierMappedSuperclassTest.EmbeddableKey.class, + GenericEmbeddedIdentifierMappedSuperclassTest.AccessReport.class, + GenericEmbeddedIdentifierMappedSuperclassTest.Group.class, + GenericEmbeddedIdentifierMappedSuperclassTest.User.class, + GenericEmbeddedIdentifierMappedSuperclassTest.UserReport.class, + GenericEmbeddedIdentifierMappedSuperclassTest.GroupReport.class, + GenericEmbeddedIdentifierMappedSuperclassTest.UserAccessReport.class, + GenericEmbeddedIdentifierMappedSuperclassTest.UserAccessReportEntity.class, + GenericEmbeddedIdentifierMappedSuperclassTest.GroupAccessReport.class, + GenericEmbeddedIdentifierMappedSuperclassTest.GroupAccessReportEntity.class +} ) +@Jira( "https://hibernate.atlassian.net/browse/HHH-16491" ) +public class GenericEmbeddedIdentifierMappedSuperclassTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final User user = new User( "user" ); + session.persist( user ); + final UserAccessReportEntity userAccessReportEntity = new UserAccessReportEntity(); + userAccessReportEntity.setId( new EmbeddableKey<>( user, new UserReport( "user_report" ), 1 ) ); + session.persist( userAccessReportEntity ); + final Group group = new Group( "group" ); + session.persist( group ); + final GroupAccessReportEntity groupAccessReportEntity = new GroupAccessReportEntity(); + groupAccessReportEntity.setId( new EmbeddableKey<>( group, new GroupReport( "group_report" ), 2 ) ); + session.persist( groupAccessReportEntity ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from GroupAccessReport" ).executeUpdate(); + session.createMutationQuery( "delete from UserAccessReport" ).executeUpdate(); + session.createMutationQuery( "delete from GroupEntity" ).executeUpdate(); + session.createMutationQuery( "delete from UserEntity" ).executeUpdate(); + } ); + } + + @Test + public void testUserReport(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final UserAccessReportEntity result = session.createQuery( + "select ur from UserAccessReport ur " + + "where ur.id.entity.login = 'user' " + + "and ur.id.embedded.userCode = 'user_report'", + UserAccessReportEntity.class + ).getSingleResult(); + assertThat( result.getId().getEntity().getLogin() ).isEqualTo( "user" ); + assertThat( result.getId().getEmbedded().getUserCode() ).isEqualTo( "user_report" ); + } ); + } + + @Test + public void testUserReportCriteria(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery( UserAccessReportEntity.class ); + final Root root = query.from( UserAccessReportEntity.class ); + final Path id = root.get( "id" ); + assertThat( id.getJavaType() ).isEqualTo( EmbeddableKey.class ); + // assert that the generic attributes inside the component's model are reported as Object type + final SqmPathSource modelPathSource = (SqmPathSource) id.getModel(); + assertThat( modelPathSource.findSubPathSource( "entity" ).getBindableJavaType() ).isEqualTo( Object.class ); + assertThat( modelPathSource.findSubPathSource( "embedded" ) + .getBindableJavaType() ).isEqualTo( Object.class ); + // the serial property is not generic, so it should have the correct type even in the generic component's model + assertThat( modelPathSource.findSubPathSource( "serial" ) + .getBindableJavaType() ).isEqualTo( Integer.class ); + // assert that types of the concrete attributes inside the component's resolved model are reported correctly + final SqmPathSource resolvedPathSource = ( (JpaPath) id ).getResolvedModel(); + assertThat( resolvedPathSource.findSubPathSource( "entity" ) + .getBindableJavaType() ).isEqualTo( User.class ); + assertThat( resolvedPathSource.findSubPathSource( "embedded" ) + .getBindableJavaType() ).isEqualTo( UserReport.class ); + assertThat( modelPathSource.findSubPathSource( "serial" ) + .getBindableJavaType() ).isEqualTo( Integer.class ); + // test same query as HQL + query.select( root ).where( + cb.and( + cb.equal( id.get( "entity" ).get( "login" ), "user" ), + cb.equal( id.get( "embedded" ).get( "userCode" ), "user_report" ) + ) + ); + final UserAccessReportEntity result = session.createQuery( query ).getSingleResult(); + assertThat( result.getId().getEntity().getLogin() ).isEqualTo( "user" ); + assertThat( result.getId().getEmbedded().getUserCode() ).isEqualTo( "user_report" ); + } ); + } + + @Test + public void testGroupReport(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final GroupAccessReportEntity result = session.createQuery( + "select gr from GroupAccessReport gr " + + "where gr.id.entity.name = 'group' " + + "and gr.id.embedded.groupCode = 'group_report'", + GroupAccessReportEntity.class + ).getSingleResult(); + assertThat( result.getId().getEntity().getName() ).isEqualTo( "group" ); + assertThat( result.getId().getEmbedded().getGroupCode() ).isEqualTo( "group_report" ); + } ); + } + + @Test + public void testGroupReportCriteria(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery( GroupAccessReportEntity.class ); + final Root root = query.from( GroupAccessReportEntity.class ); + final Path id = root.get( "id" ); + assertThat( id.getJavaType() ).isEqualTo( EmbeddableKey.class ); + // assert that the generic attributes inside the component's model are reported as Object type + final SqmPathSource modelPathSource = (SqmPathSource) id.getModel(); + assertThat( modelPathSource.findSubPathSource( "entity" ).getBindableJavaType() ).isEqualTo( Object.class ); + assertThat( modelPathSource.findSubPathSource( "embedded" ) + .getBindableJavaType() ).isEqualTo( Object.class ); + // the serial property is not generic, so it should have the correct type even in the generic component's model + assertThat( modelPathSource.findSubPathSource( "serial" ) + .getBindableJavaType() ).isEqualTo( Integer.class ); + // assert that types of the concrete attributes inside the component's resolved model are reported correctly + final SqmPathSource resolvedPathSource = ( (JpaPath) id ).getResolvedModel(); + assertThat( resolvedPathSource.findSubPathSource( "entity" ) + .getBindableJavaType() ).isEqualTo( Group.class ); + assertThat( resolvedPathSource.findSubPathSource( "embedded" ) + .getBindableJavaType() ).isEqualTo( GroupReport.class ); + assertThat( modelPathSource.findSubPathSource( "serial" ) + .getBindableJavaType() ).isEqualTo( Integer.class ); + // test same query as HQL + query.select( root ).where( + cb.and( + cb.equal( id.get( "entity" ).get( "name" ), "group" ), + cb.equal( id.get( "embedded" ).get( "groupCode" ), "group_report" ) + ) + ); + final GroupAccessReportEntity result = session.createQuery( query ).getSingleResult(); + assertThat( result.getId().getEntity().getName() ).isEqualTo( "group" ); + assertThat( result.getId().getEmbedded().getGroupCode() ).isEqualTo( "group_report" ); + } ); + } + + public static abstract class GenericObject { + private ID id; + + public ID getId() { + return id; + } + + public void setId(ID id) { + this.id = id; + } + } + + @Embeddable + public static class UserReport { + private String userCode; + + public UserReport() { + } + + public UserReport(String userCode) { + this.userCode = userCode; + } + + public String getUserCode() { + return userCode; + } + + public void setUserCode(String code) { + this.userCode = code; + } + } + + @Entity( name = "UserEntity" ) + public static class User extends GenericObject { + private String login; + + public User() { + } + + public User(String login) { + this.login = login; + } + + @Override + @Id + @GeneratedValue + public Long getId() { + return super.getId(); + } + + public void setId(Long id) { + super.setId( id ); + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + } + + @Embeddable + public static class GroupReport { + private String groupCode; + + public GroupReport() { + } + + public GroupReport(String groupCode) { + this.groupCode = groupCode; + } + + public String getGroupCode() { + return groupCode; + } + + public void setGroupCode(String code) { + this.groupCode = code; + } + } + + @Entity( name = "GroupEntity" ) + public static class Group extends GenericObject { + private String name; + + public Group() { + } + + public Group(String name) { + this.name = name; + } + + @Override + @Id + @GeneratedValue + public Long getId() { + return super.getId(); + } + + public void setId(Long id) { + super.setId( id ); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Embeddable + public static class EmbeddableKey implements Serializable { + private O entity; + private E embedded; + private Integer serial; + + public EmbeddableKey() { + } + + public EmbeddableKey(O entity, E embedded, Integer serial) { + this.entity = entity; + this.embedded = embedded; + this.serial = serial; + } + + @ManyToOne + public O getEntity() { + return entity; + } + + public void setEntity(O entity) { + this.entity = entity; + } + + @Embedded + public E getEmbedded() { + return embedded; + } + + public void setEmbedded(E embedded) { + this.embedded = embedded; + } + + public Integer getSerial() { + return serial; + } + + public void setSerial(Integer serial) { + this.serial = serial; + } + } + + @MappedSuperclass + public static abstract class AccessReport extends GenericObject> { + } + + @MappedSuperclass + public static class UserAccessReport extends AccessReport { + @Override + @EmbeddedId + @AssociationOverrides( @AssociationOverride( name = "entity", joinColumns = @JoinColumn( name = "user_id" ) ) ) + public EmbeddableKey getId() { + return super.getId(); + } + + @Override + public void setId(EmbeddableKey key) { + super.setId( key ); + } + } + + @Entity( name = "UserAccessReport" ) + public static class UserAccessReportEntity extends UserAccessReport { + } + + @MappedSuperclass + public static class GroupAccessReport extends AccessReport { + @Override + @EmbeddedId + @AssociationOverrides( @AssociationOverride( name = "entity", joinColumns = @JoinColumn( name = "group_id" ) ) ) + public EmbeddableKey getId() { + return super.getId(); + } + + @Override + public void setId(EmbeddableKey key) { + super.setId( key ); + } + } + + @Entity( name = "GroupAccessReport" ) + public static class GroupAccessReportEntity extends GroupAccessReport { + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/generics/GenericEmbeddedIdentifierTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/generics/GenericEmbeddedIdentifierTest.java new file mode 100644 index 0000000000..c994c0adda --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/generics/GenericEmbeddedIdentifierTest.java @@ -0,0 +1,373 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.annotations.embeddables.generics; + +import java.io.Serializable; + +import org.hibernate.query.criteria.JpaPath; +import org.hibernate.query.sqm.SqmPathSource; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.AssociationOverride; +import jakarta.persistence.AssociationOverrides; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Root; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel( annotatedClasses = { + GenericEmbeddedIdentifierTest.EmbeddableKey.class, + GenericEmbeddedIdentifierTest.AccessReport.class, + GenericEmbeddedIdentifierTest.Group.class, + GenericEmbeddedIdentifierTest.User.class, + GenericEmbeddedIdentifierTest.UserReport.class, + GenericEmbeddedIdentifierTest.GroupReport.class, + GenericEmbeddedIdentifierTest.GroupAccessReport.class, + GenericEmbeddedIdentifierTest.UserAccessReport.class +} ) +@Jira( "https://hibernate.atlassian.net/browse/HHH-16491" ) +public class GenericEmbeddedIdentifierTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final User user = new User( "user" ); + session.persist( user ); + final UserAccessReport userAccessReport = new UserAccessReport(); + userAccessReport.setId( new EmbeddableKey<>( user, new UserReport( "user_report" ), 1 ) ); + session.persist( userAccessReport ); + final Group group = new Group( "group" ); + session.persist( group ); + final GroupAccessReport groupAccessReport = new GroupAccessReport(); + groupAccessReport.setId( new EmbeddableKey<>( group, new GroupReport( "group_report" ), 2 ) ); + session.persist( groupAccessReport ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from GroupAccessReport" ).executeUpdate(); + session.createMutationQuery( "delete from UserAccessReport" ).executeUpdate(); + session.createMutationQuery( "delete from GroupEntity" ).executeUpdate(); + session.createMutationQuery( "delete from UserEntity" ).executeUpdate(); + } ); + } + + @Test + public void testUserReport(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final UserAccessReport result = session.createQuery( + "select ur from UserAccessReport ur " + + "where ur.id.entity.login = 'user' " + + "and ur.id.embedded.userCode = 'user_report'", + UserAccessReport.class + ).getSingleResult(); + assertThat( result.getId().getEntity().getLogin() ).isEqualTo( "user" ); + assertThat( result.getId().getEmbedded().getUserCode() ).isEqualTo( "user_report" ); + } ); + } + + @Test + public void testUserReportCriteria(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery( UserAccessReport.class ); + final Root root = query.from( UserAccessReport.class ); + final Path id = root.get( "id" ); + assertThat( id.getJavaType() ).isEqualTo( EmbeddableKey.class ); + // assert that the generic attributes inside the component's model are reported as Object type + final SqmPathSource modelPathSource = (SqmPathSource) id.getModel(); + assertThat( modelPathSource.findSubPathSource( "entity" ).getBindableJavaType() ).isEqualTo( Object.class ); + assertThat( modelPathSource.findSubPathSource( "embedded" ) + .getBindableJavaType() ).isEqualTo( Object.class ); + // the serial property is not generic, so it should have the correct type even in the generic component's model + assertThat( modelPathSource.findSubPathSource( "serial" ) + .getBindableJavaType() ).isEqualTo( Integer.class ); + // assert that types of the concrete attributes inside the component's resolved model are reported correctly + final SqmPathSource resolvedPathSource = ( (JpaPath) id ).getResolvedModel(); + assertThat( resolvedPathSource.findSubPathSource( "entity" ) + .getBindableJavaType() ).isEqualTo( User.class ); + assertThat( resolvedPathSource.findSubPathSource( "embedded" ) + .getBindableJavaType() ).isEqualTo( UserReport.class ); + assertThat( resolvedPathSource.findSubPathSource( "serial" ) + .getBindableJavaType() ).isEqualTo( Integer.class ); + // test same query as HQL + query.select( root ).where( + cb.and( + cb.equal( id.get( "entity" ).get( "login" ), "user" ), + cb.equal( id.get( "embedded" ).get( "userCode" ), "user_report" ) + ) + ); + final UserAccessReport result = session.createQuery( query ).getSingleResult(); + assertThat( result.getId().getEntity().getLogin() ).isEqualTo( "user" ); + assertThat( result.getId().getEmbedded().getUserCode() ).isEqualTo( "user_report" ); + } ); + } + + @Test + public void testGroupReport(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final GroupAccessReport result = session.createQuery( + "select gr from GroupAccessReport gr " + + "where gr.id.entity.name = 'group'" + + "and gr.id.embedded.groupCode = 'group_report'", + GroupAccessReport.class + ).getSingleResult(); + assertThat( result.getId().getEntity().getName() ).isEqualTo( "group" ); + assertThat( result.getId().getEmbedded().getGroupCode() ).isEqualTo( "group_report" ); + } ); + } + + @Test + public void testGroupReportCriteria(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery( GroupAccessReport.class ); + final Root root = query.from( GroupAccessReport.class ); + final Path id = root.get( "id" ); + assertThat( id.getJavaType() ).isEqualTo( EmbeddableKey.class ); + // assert that the generic attributes inside the component's model are reported as Object type + final SqmPathSource modelPathSource = (SqmPathSource) id.getModel(); + assertThat( modelPathSource.findSubPathSource( "entity" ).getBindableJavaType() ).isEqualTo( Object.class ); + assertThat( modelPathSource.findSubPathSource( "embedded" ) + .getBindableJavaType() ).isEqualTo( Object.class ); + // the serial property is not generic, so it should have the correct type even in the generic component's model + assertThat( modelPathSource.findSubPathSource( "serial" ) + .getBindableJavaType() ).isEqualTo( Integer.class ); + // assert that types of the concrete attributes inside the component's resolved model are reported correctly + final SqmPathSource resolvedPathSource = ( (JpaPath) id ).getResolvedModel(); + assertThat( resolvedPathSource.findSubPathSource( "entity" ) + .getBindableJavaType() ).isEqualTo( Group.class ); + assertThat( resolvedPathSource.findSubPathSource( "embedded" ) + .getBindableJavaType() ).isEqualTo( GroupReport.class ); + assertThat( resolvedPathSource.findSubPathSource( "serial" ) + .getBindableJavaType() ).isEqualTo( Integer.class ); + // test same query as HQL + query.select( root ).where( + cb.and( + cb.equal( id.get( "entity" ).get( "name" ), "group" ), + cb.equal( id.get( "embedded" ).get( "groupCode" ), "group_report" ) + ) + ); + final GroupAccessReport result = session.createQuery( query ).getSingleResult(); + assertThat( result.getId().getEntity().getName() ).isEqualTo( "group" ); + assertThat( result.getId().getEmbedded().getGroupCode() ).isEqualTo( "group_report" ); + } ); + } + + public static abstract class GenericObject { + private ID id; + + public ID getId() { + return id; + } + + public void setId(ID id) { + this.id = id; + } + } + + @Embeddable + public static class UserReport { + private String userCode; + + public UserReport() { + } + + public UserReport(String userCode) { + this.userCode = userCode; + } + + public String getUserCode() { + return userCode; + } + + public void setUserCode(String code) { + this.userCode = code; + } + } + + @Entity( name = "UserEntity" ) + public static class User extends GenericObject { + private String login; + + public User() { + } + + public User(String login) { + this.login = login; + } + + @Override + @Id + @GeneratedValue + public Long getId() { + return super.getId(); + } + + public void setId(Long id) { + super.setId( id ); + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + } + + @Embeddable + public static class GroupReport { + private String groupCode; + + public GroupReport() { + } + + public GroupReport(String groupCode) { + this.groupCode = groupCode; + } + + public String getGroupCode() { + return groupCode; + } + + public void setGroupCode(String code) { + this.groupCode = code; + } + } + + @Entity( name = "GroupEntity" ) + public static class Group extends GenericObject { + private String name; + + public Group() { + } + + public Group(String name) { + this.name = name; + } + + @Override + @Id + @GeneratedValue + public Long getId() { + return super.getId(); + } + + public void setId(Long id) { + super.setId( id ); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Embeddable + public static class EmbeddableKey implements Serializable { + private O entity; + private E embedded; + private Integer serial; + + public EmbeddableKey() { + } + + public EmbeddableKey(O entity, E embedded, Integer serial) { + this.entity = entity; + this.embedded = embedded; + this.serial = serial; + } + + @ManyToOne + public O getEntity() { + return entity; + } + + public void setEntity(O entity) { + this.entity = entity; + } + + @Embedded + public E getEmbedded() { + return embedded; + } + + public void setEmbedded(E embedded) { + this.embedded = embedded; + } + + public Integer getSerial() { + return serial; + } + + public void setSerial(Integer serial) { + this.serial = serial; + } + } + + @MappedSuperclass + public static abstract class AccessReport extends GenericObject> { + } + + @Entity( name = "UserAccessReport" ) + public static class UserAccessReport extends AccessReport { + @Override + @EmbeddedId + @AssociationOverrides( @AssociationOverride( name = "entity", joinColumns = @JoinColumn( name = "user_id" ) ) ) + public EmbeddableKey getId() { + return super.getId(); + } + + @Override + public void setId(EmbeddableKey key) { + super.setId( key ); + } + } + + @Entity( name = "GroupAccessReport" ) + public static class GroupAccessReport extends AccessReport { + @Override + @EmbeddedId + @AssociationOverrides( @AssociationOverride( name = "entity", joinColumns = @JoinColumn( name = "group_id" ) ) ) + public EmbeddableKey getId() { + return super.getId(); + } + + @Override + public void setId(EmbeddableKey key) { + super.setId( key ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/generics/GenericEmbeddedPropertyMappedSuperclassTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/generics/GenericEmbeddedPropertyMappedSuperclassTest.java new file mode 100644 index 0000000000..dc1df1cc34 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/generics/GenericEmbeddedPropertyMappedSuperclassTest.java @@ -0,0 +1,391 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.annotations.embeddables.generics; + +import java.io.Serializable; + +import org.hibernate.query.criteria.JpaPath; +import org.hibernate.query.sqm.SqmPathSource; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.AssociationOverride; +import jakarta.persistence.AssociationOverrides; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Root; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel( annotatedClasses = { + GenericEmbeddedPropertyMappedSuperclassTest.EmbeddableProp.class, + GenericEmbeddedPropertyMappedSuperclassTest.AccessReport.class, + GenericEmbeddedPropertyMappedSuperclassTest.Group.class, + GenericEmbeddedPropertyMappedSuperclassTest.User.class, + GenericEmbeddedPropertyMappedSuperclassTest.UserReport.class, + GenericEmbeddedPropertyMappedSuperclassTest.UserAccessReport.class, + GenericEmbeddedPropertyMappedSuperclassTest.UserAccessReportEntity.class, + GenericEmbeddedPropertyMappedSuperclassTest.GroupAccessReport.class, + GenericEmbeddedPropertyMappedSuperclassTest.GroupAccessReportEntity.class +} ) +@Jira( "https://hibernate.atlassian.net/browse/HHH-16491" ) +public class GenericEmbeddedPropertyMappedSuperclassTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final User user = new User( "user" ); + session.persist( user ); + final UserAccessReportEntity userAccessReportEntity = new UserAccessReportEntity(); + userAccessReportEntity.setProp( new EmbeddableProp<>( user, new UserReport( "user_report" ), 1 ) ); + session.persist( userAccessReportEntity ); + final Group group = new Group( "group" ); + session.persist( group ); + final GroupAccessReportEntity groupAccessReportEntity = new GroupAccessReportEntity(); + groupAccessReportEntity.setProp( new EmbeddableProp<>( group, new GroupReport( "group_report" ), 2 ) ); + session.persist( groupAccessReportEntity ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from GroupAccessReport" ).executeUpdate(); + session.createMutationQuery( "delete from UserAccessReport" ).executeUpdate(); + session.createMutationQuery( "delete from GroupEntity" ).executeUpdate(); + session.createMutationQuery( "delete from UserEntity" ).executeUpdate(); + } ); + } + + @Test + public void testUserReport(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final UserAccessReportEntity result = session.createQuery( + "select ur from UserAccessReport ur " + + "where ur.prop.entity.login = 'user' " + + "and ur.prop.embedded.userCode = 'user_report'", + UserAccessReportEntity.class + ).getSingleResult(); + assertThat( result.getProp().getEntity().getLogin() ).isEqualTo( "user" ); + assertThat( result.getProp().getEmbedded().getUserCode() ).isEqualTo( "user_report" ); + } ); + } + + @Test + public void testUserReportCriteria(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery( UserAccessReportEntity.class ); + final Root root = query.from( UserAccessReportEntity.class ); + final Path prop = root.get( "prop" ); + assertThat( prop.getJavaType() ).isEqualTo( EmbeddableProp.class ); + // assert that the generic attributes inside the component's model are reported as Object type + final SqmPathSource modelPathSource = (SqmPathSource) prop.getModel(); + assertThat( modelPathSource.findSubPathSource( "entity" ).getBindableJavaType() ).isEqualTo( Object.class ); + assertThat( modelPathSource.findSubPathSource( "embedded" ) + .getBindableJavaType() ).isEqualTo( Object.class ); + // the serial property is not generic, so it should have the correct type even in the generic component's model + assertThat( modelPathSource.findSubPathSource( "serial" ) + .getBindableJavaType() ).isEqualTo( Integer.class ); + // assert that types of the concrete attributes inside the component's resolved model are reported correctly + final SqmPathSource resolvedPathSource = ( (JpaPath) prop ).getResolvedModel(); + assertThat( resolvedPathSource.findSubPathSource( "entity" ) + .getBindableJavaType() ).isEqualTo( User.class ); + assertThat( resolvedPathSource.findSubPathSource( "embedded" ) + .getBindableJavaType() ).isEqualTo( UserReport.class ); + assertThat( modelPathSource.findSubPathSource( "serial" ) + .getBindableJavaType() ).isEqualTo( Integer.class ); + // test same query as HQL + query.select( root ).where( + cb.and( + cb.equal( prop.get( "entity" ).get( "login" ), "user" ), + cb.equal( prop.get( "embedded" ).get( "userCode" ), "user_report" ) + ) + ); + final UserAccessReportEntity result = session.createQuery( query ).getSingleResult(); + assertThat( result.getProp().getEntity().getLogin() ).isEqualTo( "user" ); + assertThat( result.getProp().getEmbedded().getUserCode() ).isEqualTo( "user_report" ); + } ); + } + + @Test + public void testGroupReport(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final GroupAccessReportEntity result = session.createQuery( + "select gr from GroupAccessReport gr " + + "where gr.prop.entity.name = 'group' " + + "and gr.prop.embedded.groupCode = 'group_report'", + GroupAccessReportEntity.class + ).getSingleResult(); + assertThat( result.getProp().getEntity().getName() ).isEqualTo( "group" ); + assertThat( result.getProp().getEmbedded().getGroupCode() ).isEqualTo( "group_report" ); + } ); + } + + @Test + public void testGroupReportCriteria(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery( GroupAccessReportEntity.class ); + final Root root = query.from( GroupAccessReportEntity.class ); + final Path prop = root.get( "prop" ); + assertThat( prop.getJavaType() ).isEqualTo( EmbeddableProp.class ); + // assert that the generic attributes inside the component's model are reported as Object type + final SqmPathSource modelPathSource = (SqmPathSource) prop.getModel(); + assertThat( modelPathSource.findSubPathSource( "entity" ).getBindableJavaType() ).isEqualTo( Object.class ); + assertThat( modelPathSource.findSubPathSource( "embedded" ) + .getBindableJavaType() ).isEqualTo( Object.class ); + // the serial property is not generic, so it should have the correct type even in the generic component's model + assertThat( modelPathSource.findSubPathSource( "serial" ) + .getBindableJavaType() ).isEqualTo( Integer.class ); + // assert that types of the concrete attributes inside the component's resolved model are reported correctly + final SqmPathSource resolvedPathSource = ( (JpaPath) prop ).getResolvedModel(); + assertThat( resolvedPathSource.findSubPathSource( "entity" ) + .getBindableJavaType() ).isEqualTo( Group.class ); + assertThat( resolvedPathSource.findSubPathSource( "embedded" ) + .getBindableJavaType() ).isEqualTo( GroupReport.class ); + assertThat( modelPathSource.findSubPathSource( "serial" ) + .getBindableJavaType() ).isEqualTo( Integer.class ); + // test same query as HQL + query.select( root ).where( + cb.and( + cb.equal( prop.get( "entity" ).get( "name" ), "group" ), + cb.equal( prop.get( "embedded" ).get( "groupCode" ), "group_report" ) + ) + ); + final GroupAccessReportEntity result = session.createQuery( query ).getSingleResult(); + assertThat( result.getProp().getEntity().getName() ).isEqualTo( "group" ); + assertThat( result.getProp().getEmbedded().getGroupCode() ).isEqualTo( "group_report" ); + } ); + } + + @Embeddable + public static class UserReport { + private String userCode; + + public UserReport() { + } + + public UserReport(String userCode) { + this.userCode = userCode; + } + + public String getUserCode() { + return userCode; + } + + public void setUserCode(String code) { + this.userCode = code; + } + } + + @Entity( name = "UserEntity" ) + public static class User { + private Long id; + private String login; + + public User() { + } + + public User(String login) { + this.login = login; + } + + @Id + @GeneratedValue + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + } + + @Embeddable + public static class GroupReport { + private String groupCode; + + public GroupReport() { + } + + public GroupReport(String groupCode) { + this.groupCode = groupCode; + } + + public String getGroupCode() { + return groupCode; + } + + public void setGroupCode(String code) { + this.groupCode = code; + } + } + + @Entity( name = "GroupEntity" ) + public static class Group { + private Long id; + private String name; + + public Group() { + } + + public Group(String name) { + this.name = name; + } + + @Id + @GeneratedValue + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public static abstract class GenericObject { + private T prop; + + public T getProp() { + return prop; + } + + public void setProp(T prop) { + this.prop = prop; + } + } + + @Embeddable + public static class EmbeddableProp implements Serializable { + private O entity; + private E embedded; + private Integer serial; + + public EmbeddableProp() { + } + + public EmbeddableProp(O entity, E embedded, Integer serial) { + this.entity = entity; + this.embedded = embedded; + this.serial = serial; + } + + @ManyToOne + public O getEntity() { + return entity; + } + + public void setEntity(O entity) { + this.entity = entity; + } + + public E getEmbedded() { + return embedded; + } + + public void setEmbedded(E embedded) { + this.embedded = embedded; + } + + public Integer getSerial() { + return serial; + } + + public void setSerial(Integer serial) { + this.serial = serial; + } + } + + @MappedSuperclass + public static abstract class AccessReport extends GenericObject> { + private Long id; + + @Id + @GeneratedValue + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } + + @MappedSuperclass + public static class UserAccessReport extends AccessReport { + @Override + @Embedded + @AssociationOverrides( @AssociationOverride( name = "entity", joinColumns = @JoinColumn( name = "user_id" ) ) ) + public EmbeddableProp getProp() { + return super.getProp(); + } + + @Override + public void setProp(EmbeddableProp key) { + super.setProp( key ); + } + } + + @Entity( name = "UserAccessReport" ) + public static class UserAccessReportEntity extends UserAccessReport { + } + + @MappedSuperclass + public static class GroupAccessReport extends AccessReport { + @Override + @Embedded + @AssociationOverrides( @AssociationOverride( name = "entity", joinColumns = @JoinColumn( name = "group_id" ) ) ) + public EmbeddableProp getProp() { + return super.getProp(); + } + + @Override + public void setProp(EmbeddableProp key) { + super.setProp( key ); + } + } + + @Entity( name = "GroupAccessReport" ) + public static class GroupAccessReportEntity extends GroupAccessReport { + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/generics/GenericEmbeddedPropertyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/generics/GenericEmbeddedPropertyTest.java new file mode 100644 index 0000000000..01013c2890 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/generics/GenericEmbeddedPropertyTest.java @@ -0,0 +1,381 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.annotations.embeddables.generics; + +import java.io.Serializable; + +import org.hibernate.query.criteria.JpaPath; +import org.hibernate.query.sqm.SqmPathSource; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.AssociationOverride; +import jakarta.persistence.AssociationOverrides; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Root; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel( annotatedClasses = { + GenericEmbeddedPropertyTest.EmbeddableProp.class, + GenericEmbeddedPropertyTest.AccessReport.class, + GenericEmbeddedPropertyTest.Group.class, + GenericEmbeddedPropertyTest.User.class, + GenericEmbeddedPropertyTest.UserReport.class, + GenericEmbeddedPropertyTest.GroupReport.class, + GenericEmbeddedPropertyTest.GroupAccessReport.class, + GenericEmbeddedPropertyTest.UserAccessReport.class +} ) +@Jira( "https://hibernate.atlassian.net/browse/HHH-16491" ) +public class GenericEmbeddedPropertyTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final User user = new User( "user" ); + session.persist( user ); + final UserAccessReport userAccessReport = new UserAccessReport(); + userAccessReport.setProp( new EmbeddableProp<>( user, new UserReport( "user_report" ), 1 ) ); + session.persist( userAccessReport ); + final Group group = new Group( "group" ); + session.persist( group ); + final GroupAccessReport groupAccessReport = new GroupAccessReport(); + groupAccessReport.setProp( new EmbeddableProp<>( group, new GroupReport( "group_report" ), 2 ) ); + session.persist( groupAccessReport ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from GroupAccessReport" ).executeUpdate(); + session.createMutationQuery( "delete from UserAccessReport" ).executeUpdate(); + session.createMutationQuery( "delete from GroupEntity" ).executeUpdate(); + session.createMutationQuery( "delete from UserEntity" ).executeUpdate(); + } ); + } + + @Test + public void testUserReport(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final UserAccessReport result = session.createQuery( + "select ur from UserAccessReport ur " + + "where ur.prop.entity.login = 'user' " + + "and ur.prop.embedded.userCode = 'user_report'", + UserAccessReport.class + ).getSingleResult(); + assertThat( result.getProp().getEntity().getLogin() ).isEqualTo( "user" ); + assertThat( result.getProp().getEmbedded().getUserCode() ).isEqualTo( "user_report" ); + } ); + } + + @Test + public void testUserReportCriteria(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery( UserAccessReport.class ); + final Root root = query.from( UserAccessReport.class ); + final Path prop = root.get( "prop" ); + assertThat( prop.getJavaType() ).isEqualTo( EmbeddableProp.class ); + // assert that the generic attributes inside the component's model are reported as Object type + final SqmPathSource modelPathSource = (SqmPathSource) prop.getModel(); + assertThat( modelPathSource.findSubPathSource( "entity" ).getBindableJavaType() ).isEqualTo( Object.class ); + assertThat( modelPathSource.findSubPathSource( "embedded" ) + .getBindableJavaType() ).isEqualTo( Object.class ); + // the serial property is not generic, so it should have the correct type even in the generic component's model + assertThat( modelPathSource.findSubPathSource( "serial" ) + .getBindableJavaType() ).isEqualTo( Integer.class ); + // assert that types of the concrete attributes inside the component's resolved model are reported correctly + final SqmPathSource resolvedPathSource = ( (JpaPath) prop ).getResolvedModel(); + assertThat( resolvedPathSource.findSubPathSource( "entity" ) + .getBindableJavaType() ).isEqualTo( User.class ); + assertThat( resolvedPathSource.findSubPathSource( "embedded" ) + .getBindableJavaType() ).isEqualTo( UserReport.class ); + assertThat( resolvedPathSource.findSubPathSource( "serial" ) + .getBindableJavaType() ).isEqualTo( Integer.class ); + // test same query as HQL + query.select( root ).where( + cb.and( + cb.equal( prop.get( "entity" ).get( "login" ), "user" ), + cb.equal( prop.get( "embedded" ).get( "userCode" ), "user_report" ) + ) + ); + final UserAccessReport result = session.createQuery( query ).getSingleResult(); + assertThat( result.getProp().getEntity().getLogin() ).isEqualTo( "user" ); + assertThat( result.getProp().getEmbedded().getUserCode() ).isEqualTo( "user_report" ); + } ); + } + + @Test + public void testGroupReport(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final GroupAccessReport result = session.createQuery( + "select gr from GroupAccessReport gr " + + "where gr.prop.entity.name = 'group' " + + "and gr.prop.embedded.groupCode = 'group_report'", + GroupAccessReport.class + ).getSingleResult(); + assertThat( result.getProp().getEntity().getName() ).isEqualTo( "group" ); + assertThat( result.getProp().getEmbedded().getGroupCode() ).isEqualTo( "group_report" ); + } ); + } + + @Test + public void testGroupReportCriteria(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery( GroupAccessReport.class ); + final Root root = query.from( GroupAccessReport.class ); + final Path prop = root.get( "prop" ); + assertThat( prop.getJavaType() ).isEqualTo( EmbeddableProp.class ); + // assert that the generic attributes inside the component's model are reported as Object type + final SqmPathSource modelPathSource = (SqmPathSource) prop.getModel(); + assertThat( modelPathSource.findSubPathSource( "entity" ).getBindableJavaType() ).isEqualTo( Object.class ); + assertThat( modelPathSource.findSubPathSource( "embedded" ) + .getBindableJavaType() ).isEqualTo( Object.class ); + // the serial property is not generic, so it should have the correct type even in the generic component's model + assertThat( modelPathSource.findSubPathSource( "serial" ) + .getBindableJavaType() ).isEqualTo( Integer.class ); + // assert that types of the concrete attributes inside the component's resolved model are reported correctly + final SqmPathSource resolvedPathSource = ( (JpaPath) prop ).getResolvedModel(); + assertThat( resolvedPathSource.findSubPathSource( "entity" ) + .getBindableJavaType() ).isEqualTo( Group.class ); + assertThat( resolvedPathSource.findSubPathSource( "embedded" ) + .getBindableJavaType() ).isEqualTo( GroupReport.class ); + assertThat( resolvedPathSource.findSubPathSource( "serial" ) + .getBindableJavaType() ).isEqualTo( Integer.class ); + // test same query as HQL + query.select( root ).where( + cb.and( + cb.equal( prop.get( "entity" ).get( "name" ), "group" ), + cb.equal( prop.get( "embedded" ).get( "groupCode" ), "group_report" ) + ) + ); + final GroupAccessReport result = session.createQuery( query ).getSingleResult(); + assertThat( result.getProp().getEntity().getName() ).isEqualTo( "group" ); + assertThat( result.getProp().getEmbedded().getGroupCode() ).isEqualTo( "group_report" ); + } ); + } + + @Embeddable + public static class UserReport { + private String userCode; + + public UserReport() { + } + + public UserReport(String userCode) { + this.userCode = userCode; + } + + public String getUserCode() { + return userCode; + } + + public void setUserCode(String code) { + this.userCode = code; + } + } + + @Entity( name = "UserEntity" ) + public static class User { + private Long id; + private String login; + + public User() { + } + + public User(String login) { + this.login = login; + } + + @Id + @GeneratedValue + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + } + + @Embeddable + public static class GroupReport { + private String groupCode; + + public GroupReport() { + } + + public GroupReport(String groupCode) { + this.groupCode = groupCode; + } + + public String getGroupCode() { + return groupCode; + } + + public void setGroupCode(String code) { + this.groupCode = code; + } + } + + @Entity( name = "GroupEntity" ) + public static class Group { + private Long id; + private String name; + + public Group() { + } + + public Group(String name) { + this.name = name; + } + + @Id + @GeneratedValue + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public static abstract class GenericObject { + private T prop; + + public T getProp() { + return prop; + } + + public void setProp(T prop) { + this.prop = prop; + } + } + + @Embeddable + public static class EmbeddableProp implements Serializable { + private O entity; + private E embedded; + private Integer serial; + + public EmbeddableProp() { + } + + public EmbeddableProp(O entity, E embedded, Integer serial) { + this.entity = entity; + this.embedded = embedded; + this.serial = serial; + } + + @ManyToOne + public O getEntity() { + return entity; + } + + public void setEntity(O entity) { + this.entity = entity; + } + + @Embedded + public E getEmbedded() { + return embedded; + } + + public void setEmbedded(E embedded) { + this.embedded = embedded; + } + + public Integer getSerial() { + return serial; + } + + public void setSerial(Integer serial) { + this.serial = serial; + } + } + + @MappedSuperclass + public static abstract class AccessReport extends GenericObject> { + private Long id; + + @Id + @GeneratedValue + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } + + @Entity( name = "UserAccessReport" ) + public static class UserAccessReport extends AccessReport { + @Override + @AssociationOverrides( @AssociationOverride( name = "entity", joinColumns = @JoinColumn( name = "user_id" ) ) ) + public EmbeddableProp getProp() { + return super.getProp(); + } + + @Override + public void setProp(EmbeddableProp key) { + super.setProp( key ); + } + } + + @Entity( name = "GroupAccessReport" ) + public static class GroupAccessReport extends AccessReport { + @Override + @AssociationOverrides( @AssociationOverride( name = "entity", joinColumns = @JoinColumn( name = "group_id" ) ) ) + public EmbeddableProp getProp() { + return super.getProp(); + } + + @Override + public void setProp(EmbeddableProp key) { + super.setProp( key ); + } + } +}