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) {
|
private boolean isAddNestedType(Value value) {
|
||||||
if ( value instanceof SimpleValue ) {
|
if ( value instanceof SimpleValue ) {
|
||||||
if ( ( (SimpleValue) value ).getTypeParameters() != null ) {
|
if ( ( (SimpleValue) value ).getTypeParameters() != null ) {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
package org.hibernate.envers.configuration.internal.metadata;
|
package org.hibernate.envers.configuration.internal.metadata;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.hibernate.MappingException;
|
import org.hibernate.MappingException;
|
||||||
import org.hibernate.envers.ModificationStore;
|
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.SimpleIdMapperBuilder;
|
||||||
import org.hibernate.envers.internal.entities.mapper.id.SingleIdMapper;
|
import org.hibernate.envers.internal.entities.mapper.id.SingleIdMapper;
|
||||||
import org.hibernate.envers.internal.tools.ReflectionTools;
|
import org.hibernate.envers.internal.tools.ReflectionTools;
|
||||||
|
import org.hibernate.loader.PropertyPath;
|
||||||
import org.hibernate.mapping.Component;
|
import org.hibernate.mapping.Component;
|
||||||
import org.hibernate.mapping.PersistentClass;
|
import org.hibernate.mapping.PersistentClass;
|
||||||
import org.hibernate.mapping.Property;
|
import org.hibernate.mapping.Property;
|
||||||
import org.hibernate.mapping.ToOne;
|
import org.hibernate.mapping.ToOne;
|
||||||
|
import org.hibernate.mapping.Value;
|
||||||
import org.hibernate.type.ManyToOneType;
|
import org.hibernate.type.ManyToOneType;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
|
@ -35,6 +38,7 @@ import org.dom4j.tree.DefaultElement;
|
||||||
* Generates metadata for primary identifiers (ids) of versions entities.
|
* Generates metadata for primary identifiers (ids) of versions entities.
|
||||||
*
|
*
|
||||||
* @author Adam Warski (adam at warski dot org)
|
* @author Adam Warski (adam at warski dot org)
|
||||||
|
* @author Chris Cranford
|
||||||
*/
|
*/
|
||||||
public final class IdMetadataGenerator {
|
public final class IdMetadataGenerator {
|
||||||
private final AuditMetadataGenerator mainGenerator;
|
private final AuditMetadataGenerator mainGenerator;
|
||||||
|
@ -43,51 +47,86 @@ public final class IdMetadataGenerator {
|
||||||
mainGenerator = auditMetadataGenerator;
|
mainGenerator = auditMetadataGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked"})
|
private Class<?> loadClass(Component component) {
|
||||||
private boolean addIdProperties(
|
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,
|
Element parent,
|
||||||
Iterator<Property> properties,
|
|
||||||
SimpleMapperBuilder mapper,
|
|
||||||
boolean key,
|
boolean key,
|
||||||
boolean audited) {
|
SimpleIdMapperBuilder mapper,
|
||||||
while ( properties.hasNext() ) {
|
Property mappedProperty,
|
||||||
final Property property = properties.next();
|
Property virtualProperty) {
|
||||||
final Type propertyType = property.getType();
|
|
||||||
if ( !"_identifierMapper".equals( property.getName() ) ) {
|
if ( PropertyPath.IDENTIFIER_MAPPER_PROPERTY.equals( mappedProperty.getName() ) ) {
|
||||||
boolean added = false;
|
return 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,14 +194,13 @@ public final class IdMetadataGenerator {
|
||||||
SimpleIdMapperBuilder mapper;
|
SimpleIdMapperBuilder mapper;
|
||||||
if ( idMapper != null ) {
|
if ( idMapper != null ) {
|
||||||
// Multiple id
|
// Multiple id
|
||||||
final Class componentClass = ReflectionTools.loadClass(
|
final Class componentClass = loadClass( (Component) pc.getIdentifier() );
|
||||||
( (Component) pc.getIdentifier() ).getComponentClassName(),
|
final Component virtualComponent = (Component) pc.getIdentifier();
|
||||||
mainGenerator.getClassLoaderService()
|
|
||||||
);
|
|
||||||
mapper = new MultipleIdMapper( componentClass, pc.getServiceRegistry() );
|
mapper = new MultipleIdMapper( componentClass, pc.getServiceRegistry() );
|
||||||
if ( !addIdProperties(
|
if ( !addIdProperties(
|
||||||
relIdMapping,
|
relIdMapping,
|
||||||
(Iterator<Property>) idMapper.getPropertyIterator(),
|
idMapper,
|
||||||
|
virtualComponent,
|
||||||
mapper,
|
mapper,
|
||||||
false,
|
false,
|
||||||
audited
|
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
|
// null mapper - the mapping where already added the first time, now we only want to generate the xml
|
||||||
if ( !addIdProperties(
|
if ( !addIdProperties(
|
||||||
origIdMapping,
|
origIdMapping,
|
||||||
(Iterator<Property>) idMapper.getPropertyIterator(),
|
idMapper,
|
||||||
|
virtualComponent,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
audited
|
audited
|
||||||
|
@ -184,14 +223,12 @@ public final class IdMetadataGenerator {
|
||||||
else if ( idProp.isComposite() ) {
|
else if ( idProp.isComposite() ) {
|
||||||
// Embedded id
|
// Embedded id
|
||||||
final Component idComponent = (Component) idProp.getValue();
|
final Component idComponent = (Component) idProp.getValue();
|
||||||
final Class embeddableClass = ReflectionTools.loadClass(
|
final Class embeddableClass = loadClass( idComponent );
|
||||||
idComponent.getComponentClassName(),
|
|
||||||
mainGenerator.getClassLoaderService()
|
|
||||||
);
|
|
||||||
mapper = new EmbeddedIdMapper( getIdPropertyData( idProp ), embeddableClass, pc.getServiceRegistry() );
|
mapper = new EmbeddedIdMapper( getIdPropertyData( idProp ), embeddableClass, pc.getServiceRegistry() );
|
||||||
if ( !addIdProperties(
|
if ( !addIdProperties(
|
||||||
relIdMapping,
|
relIdMapping,
|
||||||
(Iterator<Property>) idComponent.getPropertyIterator(),
|
idComponent,
|
||||||
|
null,
|
||||||
mapper,
|
mapper,
|
||||||
false,
|
false,
|
||||||
audited
|
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
|
// null mapper - the mapping where already added the first time, now we only want to generate the xml
|
||||||
if ( !addIdProperties(
|
if ( !addIdProperties(
|
||||||
origIdMapping,
|
origIdMapping,
|
||||||
(Iterator<Property>) idComponent.getPropertyIterator(),
|
idComponent,
|
||||||
|
null,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
audited
|
audited
|
||||||
|
@ -256,4 +294,45 @@ public final class IdMetadataGenerator {
|
||||||
ModificationStore.FULL, RelationTargetAuditMode.AUDITED, null, null, false
|
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() {
|
public List<AuditOverride> getAuditingOverrides() {
|
||||||
return auditJoinTableOverrides;
|
return auditJoinTableOverrides;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ public class PropertyData {
|
||||||
// They're properties used for bookkeeping by Hibernate
|
// They're properties used for bookkeeping by Hibernate
|
||||||
private boolean synthetic;
|
private boolean synthetic;
|
||||||
private Type propertyType;
|
private Type propertyType;
|
||||||
|
private Class<?> virtualReturnClass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies the given property data, except the name.
|
* Copies the given property data, except the name.
|
||||||
|
@ -42,6 +43,12 @@ public class PropertyData {
|
||||||
this.beanName = propertyData.beanName;
|
this.beanName = propertyData.beanName;
|
||||||
this.accessType = propertyData.accessType;
|
this.accessType = propertyData.accessType;
|
||||||
this.store = propertyData.store;
|
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,
|
String modifiedFlagName,
|
||||||
boolean synthetic,
|
boolean synthetic,
|
||||||
Type propertyType) {
|
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( name, beanName, accessType, store, usingModifiedFlag, modifiedFlagName, synthetic );
|
||||||
this.propertyType = propertyType;
|
this.propertyType = propertyType;
|
||||||
|
this.virtualReturnClass = virtualReturnClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
@ -132,6 +153,10 @@ public class PropertyData {
|
||||||
return propertyType;
|
return propertyType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Class<?> getVirtualReturnClass() {
|
||||||
|
return virtualReturnClass;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if ( this == o ) {
|
if ( this == o ) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ package org.hibernate.envers.internal.entities.mapper.id;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hibernate.Session;
|
||||||
import org.hibernate.envers.internal.tools.query.Parameters;
|
import org.hibernate.envers.internal.tools.query.Parameters;
|
||||||
import org.hibernate.service.ServiceRegistry;
|
import org.hibernate.service.ServiceRegistry;
|
||||||
|
|
||||||
|
@ -21,6 +22,11 @@ public interface IdMapper {
|
||||||
|
|
||||||
void mapToMapFromId(Map<String, Object> data, Object obj);
|
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);
|
void mapToMapFromEntity(Map<String, Object> data, Object obj);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -11,17 +11,44 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hibernate.Session;
|
||||||
import org.hibernate.envers.internal.entities.PropertyData;
|
import org.hibernate.envers.internal.entities.PropertyData;
|
||||||
import org.hibernate.service.ServiceRegistry;
|
import org.hibernate.service.ServiceRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Adam Warski (adam at warski dot org)
|
* @author Adam Warski (adam at warski dot org)
|
||||||
|
* @author Chris Cranford
|
||||||
*/
|
*/
|
||||||
public class MultipleIdMapper extends AbstractCompositeIdMapper implements SimpleIdMapperBuilder {
|
public class MultipleIdMapper extends AbstractCompositeIdMapper implements SimpleIdMapperBuilder {
|
||||||
public MultipleIdMapper(Class compositeIdClass, ServiceRegistry serviceRegistry) {
|
public MultipleIdMapper(Class compositeIdClass, ServiceRegistry serviceRegistry) {
|
||||||
super( compositeIdClass, 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
|
@Override
|
||||||
public void mapToMapFromId(Map<String, Object> data, Object obj) {
|
public void mapToMapFromId(Map<String, Object> data, Object obj) {
|
||||||
for ( IdMapper idMapper : ids.values() ) {
|
for ( IdMapper idMapper : ids.values() ) {
|
||||||
|
@ -31,7 +58,9 @@ public class MultipleIdMapper extends AbstractCompositeIdMapper implements Simpl
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mapToMapFromEntity(Map<String, Object> data, Object obj) {
|
public void mapToMapFromEntity(Map<String, Object> data, Object obj) {
|
||||||
mapToMapFromId( data, obj );
|
for ( IdMapper idMapper : ids.values() ) {
|
||||||
|
idMapper.mapToMapFromEntity( data, obj );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -50,7 +79,7 @@ public class MultipleIdMapper extends AbstractCompositeIdMapper implements Simpl
|
||||||
|
|
||||||
for ( PropertyData propertyData : ids.keySet() ) {
|
for ( PropertyData propertyData : ids.keySet() ) {
|
||||||
final String propertyName = propertyData.getName();
|
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;
|
return ret;
|
||||||
|
@ -83,4 +112,11 @@ public class MultipleIdMapper extends AbstractCompositeIdMapper implements Simpl
|
||||||
|
|
||||||
return ret;
|
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.RevisionType;
|
||||||
import org.hibernate.envers.boot.internal.EnversService;
|
import org.hibernate.envers.boot.internal.EnversService;
|
||||||
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
|
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
|
||||||
|
import org.hibernate.envers.internal.entities.mapper.id.IdMapper;
|
||||||
import org.hibernate.envers.strategy.AuditStrategy;
|
import org.hibernate.envers.strategy.AuditStrategy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Adam Warski (adam at warski dot org)
|
* @author Adam Warski (adam at warski dot org)
|
||||||
* @author Stephanie Pau at Markit Group Plc
|
* @author Stephanie Pau at Markit Group Plc
|
||||||
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
* @author Chris Cranford
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractAuditWorkUnit implements AuditWorkUnit {
|
public abstract class AbstractAuditWorkUnit implements AuditWorkUnit {
|
||||||
protected final SessionImplementor sessionImplementor;
|
protected final SessionImplementor sessionImplementor;
|
||||||
|
@ -52,7 +54,9 @@ public abstract class AbstractAuditWorkUnit implements AuditWorkUnit {
|
||||||
final Map<String, Object> originalId = new HashMap<>();
|
final Map<String, Object> originalId = new HashMap<>();
|
||||||
originalId.put( entitiesCfg.getRevisionFieldName(), revision );
|
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.getRevisionTypePropName(), revisionType );
|
||||||
data.put( entitiesCfg.getOriginalIdPropName(), originalId );
|
data.put( entitiesCfg.getOriginalIdPropName(), originalId );
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,15 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.envers.internal.tools;
|
package org.hibernate.envers.internal.tools;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.hibernate.annotations.common.reflection.XClass;
|
import org.hibernate.annotations.common.reflection.XClass;
|
||||||
import org.hibernate.annotations.common.reflection.XProperty;
|
import org.hibernate.annotations.common.reflection.XProperty;
|
||||||
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
|
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
|
||||||
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
|
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.internal.entities.PropertyData;
|
||||||
import org.hibernate.envers.tools.Pair;
|
import org.hibernate.envers.tools.Pair;
|
||||||
import org.hibernate.internal.util.collections.ConcurrentReferenceHashMap;
|
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 Adam Warski (adam at warski dot org)
|
||||||
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
* @author Chris Cranford
|
||||||
*/
|
*/
|
||||||
public abstract class ReflectionTools {
|
public abstract class ReflectionTools {
|
||||||
private static final Map<Pair<Class, String>, Getter> GETTER_CACHE = new ConcurrentReferenceHashMap<>(
|
private static final Map<Pair<Class, String>, Getter> GETTER_CACHE = new ConcurrentReferenceHashMap<>(
|
||||||
|
@ -74,6 +78,42 @@ public abstract class ReflectionTools {
|
||||||
return value;
|
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 clazz Source class.
|
||||||
* @param propertyName Property name.
|
* @param propertyName Property name.
|
||||||
|
|
Loading…
Reference in New Issue