Skip to content

Commit

Permalink
HHH-10667 - Fix Envers allowing @IdClass mappings using entity primar…
Browse files Browse the repository at this point in the history
…y keys.
  • Loading branch information
Naros committed Apr 18, 2018
1 parent 1ae930e commit 27a6b5d
Show file tree
Hide file tree
Showing 9 changed files with 423 additions and 82 deletions.
Expand Up @@ -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 ) {
Expand Down
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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;
Expand All @@ -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<Property> 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;
}

Expand Down Expand Up @@ -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<Property>) idMapper.getPropertyIterator(),
idMapper,
virtualComponent,
mapper,
false,
audited
Expand All @@ -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<Property>) idMapper.getPropertyIterator(),
idMapper,
virtualComponent,
null,
true,
audited
Expand All @@ -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<Property>) idComponent.getPropertyIterator(),
idComponent,
null,
mapper,
false,
audited
Expand All @@ -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<Property>) idComponent.getPropertyIterator(),
idComponent,
null,
null,
true,
audited
Expand Down Expand Up @@ -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
);
}
}
Expand Up @@ -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<AuditOverride> getAuditingOverrides() {
return auditJoinTableOverrides;
}
Expand Down
Expand Up @@ -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.
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -132,6 +153,10 @@ public Type getType() {
return propertyType;
}

public Class<?> getVirtualReturnClass() {
return virtualReturnClass;
}

@Override
public boolean equals(Object o) {
if ( this == o ) {
Expand Down
Expand Up @@ -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;

Expand All @@ -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);

/**
Expand Down

0 comments on commit 27a6b5d

Please sign in to comment.