HHH-12304 - Fix MappingException when audited property uses a custom EnumType.
This commit is contained in:
parent
8516e31d98
commit
1e9056fb03
|
@ -152,7 +152,7 @@ public final class BasicMetadataGenerator {
|
||||||
|
|
||||||
typeMapping.addAttribute( "name", typeName );
|
typeMapping.addAttribute( "name", typeName );
|
||||||
|
|
||||||
if ( EnumType.class.getName().equals( typeName ) ) {
|
if ( isEnumType( value.getType(), typeName ) ) {
|
||||||
// Proper handling of enumeration type
|
// Proper handling of enumeration type
|
||||||
mapEnumerationType( typeMapping, value.getType(), typeParameters );
|
mapEnumerationType( typeMapping, value.getType(), typeParameters );
|
||||||
}
|
}
|
||||||
|
@ -175,4 +175,21 @@ public final class BasicMetadataGenerator {
|
||||||
}
|
}
|
||||||
return typeName;
|
return typeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isEnumType(Type type, String typeName) {
|
||||||
|
// Check if a custom type implementation is used and it extends the EnumType directly.
|
||||||
|
if ( CustomType.class.isInstance( type ) ) {
|
||||||
|
final CustomType customType = (CustomType) type;
|
||||||
|
if ( EnumType.class.isInstance( customType.getUserType() ) ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if its an EnumType without a custom type
|
||||||
|
if ( EnumType.class.getName().equals( typeName ) ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
/*
|
||||||
|
* 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.envers.test.integration.customtype;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.EnumType;
|
||||||
|
import javax.persistence.Enumerated;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.Type;
|
||||||
|
import org.hibernate.annotations.TypeDef;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.engine.spi.SessionImplementor;
|
||||||
|
import org.hibernate.envers.Audited;
|
||||||
|
import org.hibernate.envers.boot.internal.EnversService;
|
||||||
|
import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase;
|
||||||
|
import org.hibernate.envers.test.Priority;
|
||||||
|
import org.hibernate.internal.SessionImpl;
|
||||||
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
|
import org.hibernate.type.CustomType;
|
||||||
|
import org.hibernate.usertype.UserType;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
|
||||||
|
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
|
||||||
|
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that a custom type which extends {@link org.hibernate.type.EnumType} continues to be
|
||||||
|
* recognized as an EnumType rather than a basic custom type implementation since the values
|
||||||
|
* which envers sends to describe the type in HBM differ whether its an Enum or not.
|
||||||
|
*
|
||||||
|
* Without the fix, this test would not even bootstrap and would throw a MappingException.
|
||||||
|
*
|
||||||
|
* @author Chris Cranford
|
||||||
|
*/
|
||||||
|
@TestForIssue(jiraKey = "HHH-12304")
|
||||||
|
public class ExtendedEnumTypeTest extends BaseEnversJPAFunctionalTestCase {
|
||||||
|
|
||||||
|
// An extended type to trigger the need for Envers to supply type information in the HBM mappings.
|
||||||
|
// This should be treated the same as any other property annotated as Enumerated or uses an Enum.
|
||||||
|
public static class ExtendedEnumType extends org.hibernate.type.EnumType {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Widget")
|
||||||
|
@TypeDef(name = "extended_enum", typeClass = ExtendedEnumType.class)
|
||||||
|
@Audited
|
||||||
|
public static class Widget {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Type(type = "extended_enum")
|
||||||
|
private Status status;
|
||||||
|
|
||||||
|
@Enumerated
|
||||||
|
@Type(type = "extended_enum")
|
||||||
|
private Status status2;
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
ARCHIVED,
|
||||||
|
DRAFT
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget(Status status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Integer id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Status getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(Status status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Status getStatus2() {
|
||||||
|
return status2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus2(Status status2) {
|
||||||
|
this.status2 = status2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class<?>[] { Widget.class };
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer widgetId;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Priority(10)
|
||||||
|
public void initData() {
|
||||||
|
// Revision 1 - insert
|
||||||
|
this.widgetId = doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
final Widget widget = new Widget( Widget.Status.DRAFT );
|
||||||
|
entityManager.persist( widget );
|
||||||
|
return widget.getId();
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Revision 2 - update
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
final Widget widget = entityManager.find( Widget.class, this.widgetId );
|
||||||
|
widget.setStatus( Widget.Status.ARCHIVED );
|
||||||
|
entityManager.merge( widget );
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Revision 3 - delete
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
final Widget widget = entityManager.find( Widget.class, this.widgetId );
|
||||||
|
entityManager.remove( widget );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRevisionHistory() {
|
||||||
|
List revisions = getAuditReader().getRevisions( Widget.class, this.widgetId );
|
||||||
|
assertEquals( Arrays.asList( 1, 2, 3 ), revisions );
|
||||||
|
|
||||||
|
final Widget rev1 = getAuditReader().find( Widget.class, this.widgetId, 1 );
|
||||||
|
assertEquals( Widget.Status.DRAFT, rev1.getStatus() );
|
||||||
|
|
||||||
|
final Widget rev2 = getAuditReader().find( Widget.class, this.widgetId, 2 );
|
||||||
|
assertEquals( Widget.Status.ARCHIVED, rev2.getStatus() );
|
||||||
|
|
||||||
|
final Widget rev3 = getAuditReader().find( Widget.class, this.widgetId, 3 );
|
||||||
|
assertNull( rev3 );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEnumPropertyStorageType() {
|
||||||
|
// test that property 'status' translates to an enum type that is stored by name (e.g. STRING)
|
||||||
|
assertEnumProperty( Widget.class, ExtendedEnumType.class, "status", EnumType.STRING );
|
||||||
|
|
||||||
|
// test that property 'status2' translates to an enum type that is stored by position (e.g. ORDINAL)
|
||||||
|
assertEnumProperty( Widget.class, ExtendedEnumType.class, "status2", EnumType.ORDINAL );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertEnumProperty(Class<?> entityClass, Class<?> typeClass, String propertyName, EnumType expectedType) {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
final SessionFactoryImplementor sessionFactory = entityManager.unwrap( SessionImplementor.class ).getSessionFactory();
|
||||||
|
|
||||||
|
final EntityPersister entityPersister = sessionFactory.getMetamodel().entityPersister( entityClass );
|
||||||
|
final EnversService enversService = sessionFactory.getServiceRegistry().getService( EnversService.class );
|
||||||
|
|
||||||
|
final String entityName = entityPersister.getEntityName();
|
||||||
|
final String auditEntityName = enversService.getAuditEntitiesConfiguration().getAuditEntityName( entityName );
|
||||||
|
|
||||||
|
final EntityPersister auditedEntityPersister = sessionFactory.getMetamodel().entityPersister( auditEntityName );
|
||||||
|
|
||||||
|
final org.hibernate.type.Type propertyType = auditedEntityPersister.getPropertyType( propertyName );
|
||||||
|
assertTyping( CustomType.class, propertyType );
|
||||||
|
|
||||||
|
final UserType userType = ( (CustomType) propertyType ).getUserType();
|
||||||
|
assertTyping( typeClass, userType );
|
||||||
|
assertTyping( org.hibernate.type.EnumType.class, userType );
|
||||||
|
|
||||||
|
switch ( expectedType ) {
|
||||||
|
case STRING:
|
||||||
|
assertTrue( !( (org.hibernate.type.EnumType) userType ).isOrdinal() );
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assertTrue( ( (org.hibernate.type.EnumType) userType ).isOrdinal() );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue