From 5c95096e7cc10a23f50e9654851e3b9fc6ee692a Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Fri, 23 Aug 2019 15:04:52 -0400 Subject: [PATCH] HHH-13564 - Fix NullPointerException for audited entity with embedded-id in mapping superclass that makes use of generics. --- .../entities/mapper/id/EmbeddedIdMapper.java | 4 +- .../embeddedid/EmbeddedIdGenericsTest.java | 219 ++++++++++++++++++ 2 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/embeddedid/EmbeddedIdGenericsTest.java diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/EmbeddedIdMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/EmbeddedIdMapper.java index ed62002ccd..abbc84872a 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/EmbeddedIdMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/EmbeddedIdMapper.java @@ -16,7 +16,6 @@ import java.util.Map; import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.internal.entities.PropertyData; import org.hibernate.envers.internal.tools.ReflectionTools; -import org.hibernate.internal.util.ReflectHelper; import org.hibernate.property.access.spi.Getter; import org.hibernate.property.access.spi.Setter; import org.hibernate.service.ServiceRegistry; @@ -73,11 +72,10 @@ public class EmbeddedIdMapper extends AbstractCompositeIdMapper implements Simpl new PrivilegedAction() { @Override public Boolean run() { - final Getter getter = ReflectionTools.getGetter( obj.getClass(), idPropertyData, getServiceRegistry() ); final Setter setter = ReflectionTools.getSetter( obj.getClass(), idPropertyData, getServiceRegistry() ); try { - final Object subObj = ReflectHelper.getDefaultConstructor( getter.getReturnType() ).newInstance(); + final Object subObj = instantiateCompositeId(); boolean ret = true; for ( IdMapper idMapper : ids.values() ) { diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/embeddedid/EmbeddedIdGenericsTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/embeddedid/EmbeddedIdGenericsTest.java new file mode 100644 index 0000000000..4f68b065a6 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/embeddedid/EmbeddedIdGenericsTest.java @@ -0,0 +1,219 @@ +/* + * 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.envers.test.integration.ids.embeddedid; + +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.RelationTargetAuditMode; +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * Tests an entity mapping that uses an {@link EmbeddedId} mapping that makes use of generics. + * + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-13564") +public class EmbeddedIdGenericsTest extends BaseEnversJPAFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { NotificationType.class, Trigger.class }; + } + + @Test + @Priority(10) + public void initData() { + // Revision 1 + // Store NotificationType and Trigger instance + doInJPA( this::entityManagerFactory, entityManager -> { + final NotificationType type = new NotificationType( "code" ); + entityManager.persist( type ); + + Trigger trigger = new Trigger( "str", type ); + entityManager.persist( trigger ); + + trigger.setActive( !trigger.isActive() ); + entityManager.merge( trigger ); + } ); + } + + @Test + public void testAuditQueryMappedSuperclassWithEmbeddedId() { + // There should be at least one revision for Trigger + List resultList = getAuditReader().createQuery().forRevisionsOfEntity( Trigger.class, true, true ).getResultList(); + assertEquals( 1, resultList.size() ); + + // Trigger should be hydrated with a composite-id values below + Trigger entityInstance = (Trigger) resultList.get( 0 ); + assertEquals( "str", entityInstance.getPk().getEventType() ); + assertEquals( "code", entityInstance.getPk().getNotificationType().getCode() ); + } + + + @MappedSuperclass + public abstract static class CompositeIdBaseEntity implements Serializable { + protected PK pk; + + @EmbeddedId + public PK getPk() { + return pk; + } + + public void setPk(PK pk) { + this.pk = pk; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + CompositeIdBaseEntity that = (CompositeIdBaseEntity) o; + return Objects.equals( pk, that.pk ); + } + + @Override + public int hashCode() { + return Objects.hash( pk ); + } + } + + @Audited + @Entity(name = "Trigger") + public static class Trigger extends CompositeIdBaseEntity { + private boolean active; + + Trigger() { + + } + + public Trigger(String eventType, NotificationType notificationType) { + this.pk = new TriggerPK( eventType, notificationType ); + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + @Embeddable + public static class TriggerPK implements Serializable { + private String eventType; + private NotificationType notificationType; + + TriggerPK() { + + } + + public TriggerPK(String eventType, NotificationType notificationType) { + this.eventType = eventType; + this.notificationType = notificationType; + } + + @Column(nullable = false, insertable = false, updatable = false) + public String getEventType() { + return eventType; + } + + public void setEventType(String eventType) { + this.eventType = eventType; + } + + @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) + @ManyToOne + @JoinColumn(insertable = false, updatable = false, nullable = false) + public NotificationType getNotificationType() { + return notificationType; + } + + public void setNotificationType(NotificationType notificationType) { + this.notificationType = notificationType; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + TriggerPK triggerPK = (TriggerPK) o; + return Objects.equals( eventType, triggerPK.eventType ) && + Objects.equals( notificationType, triggerPK.notificationType ); + } + + @Override + public int hashCode() { + return Objects.hash( eventType, notificationType ); + } + } + } + + @Entity(name = "NotificationType") + public static class NotificationType { + @Id + private String code; + + public NotificationType() { + + } + + public NotificationType(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + NotificationType that = (NotificationType) o; + return Objects.equals( code, that.code ); + } + + @Override + public int hashCode() { + return Objects.hash( code ); + } + } +}