From 27a6b5d14348820192cbd0da73bcf65402ca15d5 Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Wed, 14 Mar 2018 09:54:38 -0400 Subject: [PATCH] HHH-10667 - Fix Envers allowing @IdClass mappings using entity primary keys. --- .../metadata/BasicMetadataGenerator.java | 31 ---- .../metadata/IdMetadataGenerator.java | 175 +++++++++++++----- .../metadata/reader/PropertyAuditingData.java | 14 ++ .../internal/entities/PropertyData.java | 25 +++ .../internal/entities/mapper/id/IdMapper.java | 6 + .../entities/mapper/id/MultipleIdMapper.java | 40 +++- .../id/VirtualEntitySingleIdMapper.java | 168 +++++++++++++++++ .../work/AbstractAuditWorkUnit.java | 6 +- .../internal/tools/ReflectionTools.java | 40 ++++ 9 files changed, 423 insertions(+), 82 deletions(-) create mode 100644 hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/VirtualEntitySingleIdMapper.java diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/BasicMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/BasicMetadataGenerator.java index 244692607c78..b5c5f5edc633 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/BasicMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/BasicMetadataGenerator.java @@ -86,37 +86,6 @@ private void mapEnumerationType(Element parent, Type type, Properties parameters } } - @SuppressWarnings({"unchecked"}) - boolean addManyToOne( - Element parent, - PropertyAuditingData propertyAuditingData, - Value value, - SimpleMapperBuilder mapper) { - final Type type = value.getType(); - - // A null mapper occurs when adding to composite-id element - final Element manyToOneElement = parent.addElement( mapper != null ? "many-to-one" : "key-many-to-one" ); - manyToOneElement.addAttribute( "name", propertyAuditingData.getName() ); - manyToOneElement.addAttribute( "class", type.getName() ); - - // HHH-11107 - // Use FK hbm magic value 'none' to skip making foreign key constraints between the Envers - // schema and the base table schema when a @ManyToOne is present in an identifier. - if ( mapper == null ) { - manyToOneElement.addAttribute( "foreign-key", "none" ); - } - - MetadataTools.addColumns( manyToOneElement, value.getColumnIterator() ); - - // A null mapper means that we only want to add xml mappings - if ( mapper != null ) { - final PropertyData propertyData = propertyAuditingData.resolvePropertyData( value.getType() ); - mapper.add( propertyData ); - } - - return true; - } - private boolean isAddNestedType(Value value) { if ( value instanceof SimpleValue ) { if ( ( (SimpleValue) value ).getTypeParameters() != null ) { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/IdMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/IdMetadataGenerator.java index e9d08dc8e4f5..1f6d9919f798 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/IdMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/IdMetadataGenerator.java @@ -7,6 +7,7 @@ package org.hibernate.envers.configuration.internal.metadata; import java.util.Iterator; +import java.util.Locale; import org.hibernate.MappingException; import org.hibernate.envers.ModificationStore; @@ -21,10 +22,12 @@ import org.hibernate.envers.internal.entities.mapper.id.SimpleIdMapperBuilder; import org.hibernate.envers.internal.entities.mapper.id.SingleIdMapper; import org.hibernate.envers.internal.tools.ReflectionTools; +import org.hibernate.loader.PropertyPath; import org.hibernate.mapping.Component; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.ToOne; +import org.hibernate.mapping.Value; import org.hibernate.type.ManyToOneType; import org.hibernate.type.Type; @@ -35,6 +38,7 @@ * Generates metadata for primary identifiers (ids) of versions entities. * * @author Adam Warski (adam at warski dot org) + * @author Chris Cranford */ public final class IdMetadataGenerator { private final AuditMetadataGenerator mainGenerator; @@ -43,51 +47,86 @@ public final class IdMetadataGenerator { mainGenerator = auditMetadataGenerator; } - @SuppressWarnings({"unchecked"}) + private Class loadClass(Component component) { + final String className = component.getComponentClassName(); + return ReflectionTools.loadClass( className, mainGenerator.getClassLoaderService() ); + } + + private static boolean isSameType(Property left, Property right) { + return left.getType().getName().equals( right.getType().getName() ); + } + + private boolean addIdProperty( + Element parent, + boolean key, + SimpleIdMapperBuilder mapper, + Property mappedProperty, + Property virtualProperty) { + + if ( PropertyPath.IDENTIFIER_MAPPER_PROPERTY.equals( mappedProperty.getName() ) ) { + return false; + } + + final PropertyAuditingData propertyAuditingData = getIdPersistentPropertyAuditingData( mappedProperty ); + + if ( ManyToOneType.class.isInstance( mappedProperty.getType() ) ) { + // This can technically be a @ManyToOne or logical @OneToOne + final boolean added = addManyToOne( parent, propertyAuditingData, mappedProperty.getValue(), mapper ); + if ( added && mapper != null ) { + if ( virtualProperty != null && !isSameType( mappedProperty, virtualProperty ) ) { + // A virtual property is only available when an @IdClass is used. We specifically need to map + // both the value and virtual types when they differ so we can adequately map between them at + // appropriate points. + final Type valueType = mappedProperty.getType(); + final Type virtualValueType = virtualProperty.getType(); + mapper.add( propertyAuditingData.resolvePropertyData( valueType, virtualValueType ) ); + } + else { + // In this branch the identifier either doesn't use an @IdClass or the property types between + // the @IdClass and containing entity are identical, allowing us to use prior behavior. + mapper.add( propertyAuditingData.resolvePropertyData( mappedProperty.getType() ) ); + } + } + + return added; + } + + return addBasic( parent, propertyAuditingData, mappedProperty.getValue(), mapper, key ); + } + private boolean addIdProperties( Element parent, - Iterator properties, - SimpleMapperBuilder mapper, + Component component, + Component virtualComponent, + SimpleIdMapperBuilder mapper, boolean key, boolean audited) { + final Iterator properties = component.getPropertyIterator(); while ( properties.hasNext() ) { - final Property property = properties.next(); - final Type propertyType = property.getType(); - if ( !"_identifierMapper".equals( property.getName() ) ) { - boolean added = false; - if ( propertyType instanceof ManyToOneType ) { - added = mainGenerator.getBasicMetadataGenerator().addManyToOne( - parent, - getIdPersistentPropertyAuditingData( property ), - property.getValue(), - mapper - ); - } - else { - // Last but one parameter: ids are always insertable - added = mainGenerator.getBasicMetadataGenerator().addBasic( - parent, - getIdPersistentPropertyAuditingData( property ), - property.getValue(), - mapper, - true, - key + final Property property = (Property) properties.next(); + + final Property virtualProperty; + if ( virtualComponent != null ) { + virtualProperty = virtualComponent.getProperty( property.getName() ); + } + else { + virtualProperty = null; + } + + if ( !addIdProperty( parent, key, mapper, property, virtualProperty ) ) { + // If the entity is audited, and a non-supported id component is used, throw exception. + if ( audited ) { + throw new MappingException( + String.format( + Locale.ROOT, + "Type not supported: %s", + property.getType().getClass().getName() + ) ); } - if ( !added ) { - // If the entity is audited, and a non-supported id component is used, throwing an exception. - // If the entity is not audited, then we simply don't support this entity, even in - // target relation mode not audited. - if ( audited ) { - throw new MappingException( "Type not supported: " + propertyType.getClass().getName() ); - } - else { - return false; - } - } + return false; } } - return true; } @@ -155,14 +194,13 @@ IdMappingData addId(PersistentClass pc, boolean audited) { SimpleIdMapperBuilder mapper; if ( idMapper != null ) { // Multiple id - final Class componentClass = ReflectionTools.loadClass( - ( (Component) pc.getIdentifier() ).getComponentClassName(), - mainGenerator.getClassLoaderService() - ); + final Class componentClass = loadClass( (Component) pc.getIdentifier() ); + final Component virtualComponent = (Component) pc.getIdentifier(); mapper = new MultipleIdMapper( componentClass, pc.getServiceRegistry() ); if ( !addIdProperties( relIdMapping, - (Iterator) idMapper.getPropertyIterator(), + idMapper, + virtualComponent, mapper, false, audited @@ -173,7 +211,8 @@ IdMappingData addId(PersistentClass pc, boolean audited) { // null mapper - the mapping where already added the first time, now we only want to generate the xml if ( !addIdProperties( origIdMapping, - (Iterator) idMapper.getPropertyIterator(), + idMapper, + virtualComponent, null, true, audited @@ -184,14 +223,12 @@ IdMappingData addId(PersistentClass pc, boolean audited) { else if ( idProp.isComposite() ) { // Embedded id final Component idComponent = (Component) idProp.getValue(); - final Class embeddableClass = ReflectionTools.loadClass( - idComponent.getComponentClassName(), - mainGenerator.getClassLoaderService() - ); + final Class embeddableClass = loadClass( idComponent ); mapper = new EmbeddedIdMapper( getIdPropertyData( idProp ), embeddableClass, pc.getServiceRegistry() ); if ( !addIdProperties( relIdMapping, - (Iterator) idComponent.getPropertyIterator(), + idComponent, + null, mapper, false, audited @@ -202,7 +239,8 @@ else if ( idProp.isComposite() ) { // null mapper - the mapping where already added the first time, now we only want to generate the xml if ( !addIdProperties( origIdMapping, - (Iterator) idComponent.getPropertyIterator(), + idComponent, + null, null, true, audited @@ -256,4 +294,45 @@ private PropertyAuditingData getIdPersistentPropertyAuditingData(Property proper ModificationStore.FULL, RelationTargetAuditMode.AUDITED, null, null, false ); } + + @SuppressWarnings({"unchecked"}) + boolean addManyToOne( + Element parent, + PropertyAuditingData propertyAuditingData, + Value value, + SimpleMapperBuilder mapper) { + final Type type = value.getType(); + + // A null mapper occurs when adding to composite-id element + final Element manyToOneElement = parent.addElement( mapper != null ? "many-to-one" : "key-many-to-one" ); + manyToOneElement.addAttribute( "name", propertyAuditingData.getName() ); + manyToOneElement.addAttribute( "class", type.getName() ); + + // HHH-11107 + // Use FK hbm magic value 'none' to skip making foreign key constraints between the Envers + // schema and the base table schema when a @ManyToOne is present in an identifier. + if ( mapper == null ) { + manyToOneElement.addAttribute( "foreign-key", "none" ); + } + + MetadataTools.addColumns( manyToOneElement, value.getColumnIterator() ); + + return true; + } + + boolean addBasic( + Element parent, + PropertyAuditingData propertyAuditingData, + Value value, + SimpleIdMapperBuilder mapper, + boolean key) { + return mainGenerator.getBasicMetadataGenerator().addBasic( + parent, + propertyAuditingData, + value, + mapper, + true, + key + ); + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/PropertyAuditingData.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/PropertyAuditingData.java index dff0579b53df..cef9147d2d1f 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/PropertyAuditingData.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/PropertyAuditingData.java @@ -160,6 +160,20 @@ public PropertyData resolvePropertyData(Type propertyType) { ); } + public PropertyData resolvePropertyData(Type propertyType, Type virtualType) { + return new PropertyData( + name, + beanName, + accessType, + store, + usingModifiedFlag, + modifiedFlagName, + syntheic, + propertyType, + virtualType.getReturnedClass() + ); + } + public List getAuditingOverrides() { return auditJoinTableOverrides; } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/PropertyData.java b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/PropertyData.java index 9d0854427532..fb8ac704adee 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/PropertyData.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/PropertyData.java @@ -30,6 +30,7 @@ public class PropertyData { // They're properties used for bookkeeping by Hibernate private boolean synthetic; private Type propertyType; + private Class virtualReturnClass; /** * Copies the given property data, except the name. @@ -42,6 +43,12 @@ public PropertyData(String newName, PropertyData propertyData) { this.beanName = propertyData.beanName; this.accessType = propertyData.accessType; this.store = propertyData.store; + + this.usingModifiedFlag = propertyData.usingModifiedFlag; + this.modifiedFlagName = propertyData.modifiedFlagName; + this.synthetic = propertyData.synthetic; + this.propertyType = propertyData.propertyType; + this.virtualReturnClass = propertyData.virtualReturnClass; } /** @@ -92,8 +99,22 @@ public PropertyData( String modifiedFlagName, boolean synthetic, Type propertyType) { + this( name, beanName, accessType, store, usingModifiedFlag, modifiedFlagName, synthetic, propertyType, null ); + } + + public PropertyData( + String name, + String beanName, + String accessType, + ModificationStore store, + boolean usingModifiedFlag, + String modifiedFlagName, + boolean synthetic, + Type propertyType, + Class virtualReturnClass) { this( name, beanName, accessType, store, usingModifiedFlag, modifiedFlagName, synthetic ); this.propertyType = propertyType; + this.virtualReturnClass = virtualReturnClass; } public String getName() { @@ -132,6 +153,10 @@ public Type getType() { return propertyType; } + public Class getVirtualReturnClass() { + return virtualReturnClass; + } + @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/IdMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/IdMapper.java index a2286660ec38..d447a3f7ae1a 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/IdMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/IdMapper.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Map; +import org.hibernate.Session; import org.hibernate.envers.internal.tools.query.Parameters; import org.hibernate.service.ServiceRegistry; @@ -21,6 +22,11 @@ public interface IdMapper { void mapToMapFromId(Map data, Object obj); + default void mapToMapFromId(Session session, Map data, Object obj) { + // Delegate to the old behavior, allowing implementations to override. + mapToMapFromId( data, obj ); + } + void mapToMapFromEntity(Map data, Object obj); /** diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/MultipleIdMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/MultipleIdMapper.java index defd07dee5cc..f7bea641e517 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/MultipleIdMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/MultipleIdMapper.java @@ -11,17 +11,44 @@ import java.util.List; import java.util.Map; +import org.hibernate.Session; import org.hibernate.envers.internal.entities.PropertyData; import org.hibernate.service.ServiceRegistry; /** * @author Adam Warski (adam at warski dot org) + * @author Chris Cranford */ public class MultipleIdMapper extends AbstractCompositeIdMapper implements SimpleIdMapperBuilder { public MultipleIdMapper(Class compositeIdClass, ServiceRegistry serviceRegistry) { super( compositeIdClass, serviceRegistry ); } + @Override + public void add(PropertyData propertyData) { + ids.put( propertyData, resolveIdMapper( propertyData ) ); + } + + @Override + public void mapToMapFromId(Session session, Map data, Object obj) { + if ( compositeIdClass.isInstance( obj ) ) { + for ( Map.Entry entry : ids.entrySet() ) { + final PropertyData propertyData = entry.getKey(); + final SingleIdMapper idMapper = entry.getValue(); + + if ( propertyData.getVirtualReturnClass() == null ) { + idMapper.mapToMapFromEntity( data, obj ); + } + else { + idMapper.mapToMapFromId( session, data, obj ); + } + } + } + else { + mapToMapFromId( data, obj ); + } + } + @Override public void mapToMapFromId(Map data, Object obj) { for ( IdMapper idMapper : ids.values() ) { @@ -31,7 +58,9 @@ public void mapToMapFromId(Map data, Object obj) { @Override public void mapToMapFromEntity(Map data, Object obj) { - mapToMapFromId( data, obj ); + for ( IdMapper idMapper : ids.values() ) { + idMapper.mapToMapFromEntity( data, obj ); + } } @Override @@ -50,7 +79,7 @@ public IdMapper prefixMappedProperties(String prefix) { for ( PropertyData propertyData : ids.keySet() ) { final String propertyName = propertyData.getName(); - ret.ids.put( propertyData, new SingleIdMapper( getServiceRegistry(), new PropertyData( prefix + propertyName, propertyData ) ) ); + ret.ids.put( propertyData, resolveIdMapper( new PropertyData( prefix + propertyName, propertyData ) ) ); } return ret; @@ -83,4 +112,11 @@ public List mapToQueryParametersFromId(Object obj) { return ret; } + + private SingleIdMapper resolveIdMapper(PropertyData propertyData) { + if ( propertyData.getVirtualReturnClass() != null ) { + return new VirtualEntitySingleIdMapper( getServiceRegistry(), propertyData ); + } + return new SingleIdMapper( getServiceRegistry(), propertyData ); + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/VirtualEntitySingleIdMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/VirtualEntitySingleIdMapper.java new file mode 100644 index 000000000000..587fac7aa983 --- /dev/null +++ b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/VirtualEntitySingleIdMapper.java @@ -0,0 +1,168 @@ +/* + * 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.internal.entities.mapper.id; + +import java.io.Serializable; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Map; + +import org.hibernate.Session; +import org.hibernate.envers.boot.internal.EnversService; +import org.hibernate.envers.internal.entities.PropertyData; +import org.hibernate.envers.internal.tools.ReflectionTools; +import org.hibernate.property.access.spi.Getter; +import org.hibernate.property.access.spi.Setter; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.type.EntityType; + +/** + * An extension to the {@link SingleIdMapper} implementation that supports the use case of an {@code @IdClass} + * mapping that contains an entity association where the {@code @IdClass} stores the primary key of the + * associated entity rather than the entity object itself. + * + * Internally this mapper is capable of transforming the primary key values into the associated entity object + * and vice versa depending upon the operation. + * + * @author Chris Cranford + */ +public class VirtualEntitySingleIdMapper extends SingleIdMapper { + + private final PropertyData propertyData; + private final String entityName; + + private IdMapper entityIdMapper; + + public VirtualEntitySingleIdMapper(ServiceRegistry serviceRegistry, PropertyData propertyData) { + super( serviceRegistry, propertyData ); + this.propertyData = propertyData; + this.entityName = resolveEntityName( this.propertyData ); + } + + @Override + public void mapToMapFromId(Session session, Map data, Object obj) { + final Serializable value = AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Serializable run() { + final Getter getter = ReflectionTools.getGetter( + obj.getClass(), + propertyData, + getServiceRegistry() + ); + return (Serializable) getter.get( obj ); + } + } + ); + + // Either loads the entity from the session's 1LC if it already exists or potentially creates a + // proxy object to represent the entity by identifier so that we can reference it in the map. + final Object entity = session.load( this.entityName, value ); + data.put( propertyData.getName(), entity ); + } + + @Override + public boolean mapToEntityFromMap(Object obj, Map data) { + if ( data == null || obj == null ) { + return false; + } + + final Object value = data.get( propertyData.getName() ); + if ( value == null ) { + return false; + } + + return AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Boolean run() { + final Setter setter = ReflectionTools.getSetter( + obj.getClass(), + propertyData, + getServiceRegistry() + ); + final Class paramClass = ReflectionTools.getType( + obj.getClass(), + propertyData, + getServiceRegistry() + ); + + if ( paramClass != null && paramClass.equals( propertyData.getVirtualReturnClass() ) ) { + setter.set( obj, getAssociatedEntityIdMapper().mapToIdFromEntity( value ), null ); + } + else { + setter.set( obj, value, null ); + } + + return true; + } + } + ); + } + + @Override + public void mapToMapFromEntity(Map data, Object obj) { + if ( obj == null ) { + data.put( propertyData.getName(), null ); + } + else { + if ( obj instanceof HibernateProxy ) { + final HibernateProxy proxy = (HibernateProxy) obj; + data.put( propertyData.getName(), proxy.getHibernateLazyInitializer().getIdentifier() ); + } + else { + final Object value = AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Object run() { + final Getter getter = ReflectionTools.getGetter( + obj.getClass(), + propertyData, + getServiceRegistry() + ); + return getter.get( obj ); + } + } + ); + + if ( propertyData.getVirtualReturnClass().isInstance( value ) ) { + // The value is the primary key, need to map it via IdMapper + getPrefixedAssociatedEntityIdMapper( propertyData ).mapToMapFromId( data, value ); + } + else { + data.put( propertyData.getName(), value ); + } + } + } + } + + private IdMapper getAssociatedEntityIdMapper() { + if ( entityIdMapper == null ) { + entityIdMapper = resolveEntityIdMapper( getServiceRegistry(), entityName ); + } + return entityIdMapper; + } + + private IdMapper getPrefixedAssociatedEntityIdMapper(PropertyData propertyData) { + return getAssociatedEntityIdMapper().prefixMappedProperties( propertyData.getName() + "." ); + } + + private static String resolveEntityName(PropertyData propertyData) { + if ( EntityType.class.isInstance( propertyData.getType() ) ) { + final EntityType entityType = (EntityType) propertyData.getType(); + return entityType.getAssociatedEntityName(); + } + return null; + } + + private static IdMapper resolveEntityIdMapper(ServiceRegistry serviceRegistry, String entityName) { + final EnversService enversService = serviceRegistry.getService( EnversService.class ); + return enversService.getEntitiesConfigurations().get( entityName ).getIdMapper(); + } + +} diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/internal/synchronization/work/AbstractAuditWorkUnit.java b/hibernate-envers/src/main/java/org/hibernate/envers/internal/synchronization/work/AbstractAuditWorkUnit.java index 0fdfe666c065..b305edf778e5 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/internal/synchronization/work/AbstractAuditWorkUnit.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/internal/synchronization/work/AbstractAuditWorkUnit.java @@ -15,12 +15,14 @@ import org.hibernate.envers.RevisionType; import org.hibernate.envers.boot.internal.EnversService; import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration; +import org.hibernate.envers.internal.entities.mapper.id.IdMapper; import org.hibernate.envers.strategy.AuditStrategy; /** * @author Adam Warski (adam at warski dot org) * @author Stephanie Pau at Markit Group Plc * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + * @author Chris Cranford */ public abstract class AbstractAuditWorkUnit implements AuditWorkUnit { protected final SessionImplementor sessionImplementor; @@ -52,7 +54,9 @@ protected void fillDataWithId(Map data, Object revision) { final Map originalId = new HashMap<>(); originalId.put( entitiesCfg.getRevisionFieldName(), revision ); - enversService.getEntitiesConfigurations().get( getEntityName() ).getIdMapper().mapToMapFromId( originalId, id ); + final IdMapper idMapper = enversService.getEntitiesConfigurations().get( getEntityName() ).getIdMapper(); + idMapper.mapToMapFromId( sessionImplementor, originalId, id ); + data.put( entitiesCfg.getRevisionTypePropName(), revisionType ); data.put( entitiesCfg.getOriginalIdPropName(), originalId ); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/internal/tools/ReflectionTools.java b/hibernate-envers/src/main/java/org/hibernate/envers/internal/tools/ReflectionTools.java index 72c6bd7d4ae5..08e0c2cd6103 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/internal/tools/ReflectionTools.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/internal/tools/ReflectionTools.java @@ -6,12 +6,15 @@ */ package org.hibernate.envers.internal.tools; +import java.lang.reflect.Field; +import java.util.Locale; import java.util.Map; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.internal.entities.PropertyData; import org.hibernate.envers.tools.Pair; import org.hibernate.internal.util.collections.ConcurrentReferenceHashMap; @@ -24,6 +27,7 @@ /** * @author Adam Warski (adam at warski dot org) * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + * @author Chris Cranford */ public abstract class ReflectionTools { private static final Map, Getter> GETTER_CACHE = new ConcurrentReferenceHashMap<>( @@ -74,6 +78,42 @@ public static Setter getSetter(Class cls, String propertyName, String accessorTy return value; } + public static Field getField(Class cls, PropertyData propertyData) { + Field field = null; + Class clazz = cls; + while ( clazz != null && field == null ) { + try { + field = clazz.getDeclaredField( propertyData.getName() ); + } + catch ( Exception e ) { + // ignore + } + clazz = clazz.getSuperclass(); + } + return field; + } + + public static Class getType(Class cls, PropertyData propertyData, ServiceRegistry serviceRegistry) { + final Setter setter = getSetter( cls, propertyData, serviceRegistry ); + if ( setter.getMethod() != null && setter.getMethod().getParameterCount() > 0 ) { + return setter.getMethod().getParameterTypes()[0]; + } + + final Field field = getField( cls, propertyData ); + if ( field != null ) { + return field.getType(); + } + + throw new AuditException( + String.format( + Locale.ROOT, + "Failed to determine type for field [%s] on class [%s].", + propertyData.getName(), + cls.getName() + ) + ); + } + /** * @param clazz Source class. * @param propertyName Property name.