HHH-10667 - Fix Envers allowing @IdClass mappings using entity primary keys.
This commit is contained in:
parent
1ae930ef69
commit
27a6b5d143
|
@ -86,37 +86,6 @@ public final class BasicMetadataGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
@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 ) {
|
||||
|
|
|
@ -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.MultipleIdMapper;
|
|||
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 @@ import org.dom4j.tree.DefaultElement;
|
|||
* 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 boolean addIdProperties(
|
||||
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,
|
||||
Iterator<Property> properties,
|
||||
SimpleMapperBuilder mapper,
|
||||
boolean key,
|
||||
boolean audited) {
|
||||
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
|
||||
);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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,
|
||||
Component component,
|
||||
Component virtualComponent,
|
||||
SimpleIdMapperBuilder mapper,
|
||||
boolean key,
|
||||
boolean audited) {
|
||||
final Iterator properties = component.getPropertyIterator();
|
||||
while ( properties.hasNext() ) {
|
||||
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()
|
||||
)
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -155,14 +194,13 @@ public final class IdMetadataGenerator {
|
|||
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<Property>) idMapper.getPropertyIterator(),
|
||||
idMapper,
|
||||
virtualComponent,
|
||||
mapper,
|
||||
false,
|
||||
audited
|
||||
|
@ -173,7 +211,8 @@ public final class IdMetadataGenerator {
|
|||
// null mapper - the mapping where already added the first time, now we only want to generate the xml
|
||||
if ( !addIdProperties(
|
||||
origIdMapping,
|
||||
(Iterator<Property>) idMapper.getPropertyIterator(),
|
||||
idMapper,
|
||||
virtualComponent,
|
||||
null,
|
||||
true,
|
||||
audited
|
||||
|
@ -184,14 +223,12 @@ public final class IdMetadataGenerator {
|
|||
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<Property>) idComponent.getPropertyIterator(),
|
||||
idComponent,
|
||||
null,
|
||||
mapper,
|
||||
false,
|
||||
audited
|
||||
|
@ -202,7 +239,8 @@ public final class IdMetadataGenerator {
|
|||
// null mapper - the mapping where already added the first time, now we only want to generate the xml
|
||||
if ( !addIdProperties(
|
||||
origIdMapping,
|
||||
(Iterator<Property>) idComponent.getPropertyIterator(),
|
||||
idComponent,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
audited
|
||||
|
@ -256,4 +294,45 @@ public final class IdMetadataGenerator {
|
|||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -160,6 +160,20 @@ public class PropertyAuditingData {
|
|||
);
|
||||
}
|
||||
|
||||
public PropertyData resolvePropertyData(Type propertyType, Type virtualType) {
|
||||
return new PropertyData(
|
||||
name,
|
||||
beanName,
|
||||
accessType,
|
||||
store,
|
||||
usingModifiedFlag,
|
||||
modifiedFlagName,
|
||||
syntheic,
|
||||
propertyType,
|
||||
virtualType.getReturnedClass()
|
||||
);
|
||||
}
|
||||
|
||||
public List<AuditOverride> getAuditingOverrides() {
|
||||
return auditJoinTableOverrides;
|
||||
}
|
||||
|
|
|
@ -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 class 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 class 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 class PropertyData {
|
|||
return propertyType;
|
||||
}
|
||||
|
||||
public Class<?> getVirtualReturnClass() {
|
||||
return virtualReturnClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if ( this == o ) {
|
||||
|
|
|
@ -9,6 +9,7 @@ package org.hibernate.envers.internal.entities.mapper.id;
|
|||
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<String, Object> data, Object obj);
|
||||
|
||||
default void mapToMapFromId(Session session, Map<String, Object> data, Object obj) {
|
||||
// Delegate to the old behavior, allowing implementations to override.
|
||||
mapToMapFromId( data, obj );
|
||||
}
|
||||
|
||||
void mapToMapFromEntity(Map<String, Object> data, Object obj);
|
||||
|
||||
/**
|
||||
|
|
|
@ -11,17 +11,44 @@ import java.util.LinkedHashMap;
|
|||
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<String, Object> data, Object obj) {
|
||||
if ( compositeIdClass.isInstance( obj ) ) {
|
||||
for ( Map.Entry<PropertyData, SingleIdMapper> 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<String, Object> data, Object obj) {
|
||||
for ( IdMapper idMapper : ids.values() ) {
|
||||
|
@ -31,7 +58,9 @@ public class MultipleIdMapper extends AbstractCompositeIdMapper implements Simpl
|
|||
|
||||
@Override
|
||||
public void mapToMapFromEntity(Map<String, Object> data, Object obj) {
|
||||
mapToMapFromId( data, obj );
|
||||
for ( IdMapper idMapper : ids.values() ) {
|
||||
idMapper.mapToMapFromEntity( data, obj );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -50,7 +79,7 @@ public class MultipleIdMapper extends AbstractCompositeIdMapper implements Simpl
|
|||
|
||||
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 class MultipleIdMapper extends AbstractCompositeIdMapper implements Simpl
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private SingleIdMapper resolveIdMapper(PropertyData propertyData) {
|
||||
if ( propertyData.getVirtualReturnClass() != null ) {
|
||||
return new VirtualEntitySingleIdMapper( getServiceRegistry(), propertyData );
|
||||
}
|
||||
return new SingleIdMapper( getServiceRegistry(), propertyData );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
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<String, Object> data, Object obj) {
|
||||
final Serializable value = AccessController.doPrivileged(
|
||||
new PrivilegedAction<Serializable>() {
|
||||
@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<Boolean>() {
|
||||
@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<String, Object> 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<Object>() {
|
||||
@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();
|
||||
}
|
||||
|
||||
}
|
|
@ -15,12 +15,14 @@ import org.hibernate.engine.spi.SessionImplementor;
|
|||
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 @@ public abstract class AbstractAuditWorkUnit implements AuditWorkUnit {
|
|||
final Map<String, Object> 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 );
|
||||
}
|
||||
|
|
|
@ -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 @@ import org.hibernate.service.ServiceRegistry;
|
|||
/**
|
||||
* @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<Pair<Class, String>, Getter> GETTER_CACHE = new ConcurrentReferenceHashMap<>(
|
||||
|
@ -74,6 +78,42 @@ public abstract class ReflectionTools {
|
|||
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.
|
||||
|
|
Loading…
Reference in New Issue