HHH-9108 - Fix PropertyAccessException when auditing an Embeddable that contains an associative collection.

This commit is contained in:
Chris Cranford 2016-12-23 00:44:10 -05:00
parent 61ce4b86ff
commit 652f85644f
4 changed files with 49 additions and 14 deletions

View File

@ -14,10 +14,13 @@ import java.util.Map;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.envers.ModificationStore; import org.hibernate.envers.ModificationStore;
import org.hibernate.envers.RelationTargetAuditMode; import org.hibernate.envers.RelationTargetAuditMode;
import org.hibernate.envers.configuration.internal.metadata.reader.AuditedPropertiesHolder;
import org.hibernate.envers.configuration.internal.metadata.reader.ClassAuditingData; import org.hibernate.envers.configuration.internal.metadata.reader.ClassAuditingData;
import org.hibernate.envers.configuration.internal.metadata.reader.ComponentAuditingData;
import org.hibernate.envers.configuration.internal.metadata.reader.PropertyAuditingData; import org.hibernate.envers.configuration.internal.metadata.reader.PropertyAuditingData;
import org.hibernate.envers.internal.EnversMessageLogger; import org.hibernate.envers.internal.EnversMessageLogger;
import org.hibernate.envers.internal.tools.MappingTools; import org.hibernate.envers.internal.tools.MappingTools;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.List; import org.hibernate.mapping.List;
import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.PersistentClass;
@ -78,31 +81,26 @@ public class ClassesAuditingData {
for ( Map.Entry<PersistentClass, ClassAuditingData> classAuditingDataEntry : persistentClassToAuditingData.entrySet() ) { for ( Map.Entry<PersistentClass, ClassAuditingData> classAuditingDataEntry : persistentClassToAuditingData.entrySet() ) {
final PersistentClass pc = classAuditingDataEntry.getKey(); final PersistentClass pc = classAuditingDataEntry.getKey();
final ClassAuditingData classAuditingData = classAuditingDataEntry.getValue(); final ClassAuditingData classAuditingData = classAuditingDataEntry.getValue();
for ( String propertyName : classAuditingData.getPropertyNames() ) { for ( String propertyName : classAuditingData.getNonSyntheticPropertyNames() ) {
updateCalculatedProperty( pc, classAuditingData, propertyName ); final Property property = pc.getProperty( propertyName );
updateCalculatedProperty( pc.getEntityName(), property, propertyName, classAuditingData );
} }
} }
} }
private void updateCalculatedProperty( private void updateCalculatedProperty(String entityName, Property property, String propertyName, AuditedPropertiesHolder propertyHolder) {
PersistentClass pc, final PropertyAuditingData propertyAuditingData = propertyHolder.getPropertyAuditingData( propertyName );
ClassAuditingData classAuditingData,
String propertyName) {
final PropertyAuditingData propertyAuditingData = classAuditingData.getPropertyAuditingData( propertyName );
final boolean isAuditMappedBy = propertyAuditingData.getAuditMappedBy() != null; final boolean isAuditMappedBy = propertyAuditingData.getAuditMappedBy() != null;
final boolean isRelationMappedBy = propertyAuditingData.getRelationMappedBy() != null; final boolean isRelationMappedBy = propertyAuditingData.getRelationMappedBy() != null;
// handle updating the property, if applicable.
if ( isAuditMappedBy || isRelationMappedBy ) { if ( isAuditMappedBy || isRelationMappedBy ) {
final Property property = pc.getProperty( propertyName );
final String referencedEntityName = MappingTools.getReferencedEntityName( property.getValue() ); final String referencedEntityName = MappingTools.getReferencedEntityName( property.getValue() );
final ClassAuditingData referencedAuditData = entityNameToAuditingData.get( referencedEntityName ); final ClassAuditingData referencedAuditData = entityNameToAuditingData.get( referencedEntityName );
if ( isAuditMappedBy ) { if ( isAuditMappedBy ) {
// If a property had the @AuditMappedBy annotation, setting the referenced fields to be always insertable. // If a property had the @AuditMappedBy annotation, setting the referenced fields to be always insertable.
setAuditMappedByInsertable( referencedEntityName, pc.getEntityName(), referencedAuditData, propertyAuditingData ); setAuditMappedByInsertable( referencedEntityName, entityName, referencedAuditData, propertyAuditingData );
} }
else if ( isRelationMappedBy && ( property.getValue() instanceof List ) ) { else if ( isRelationMappedBy && ( property.getValue() instanceof List ) ) {
// If a property has mappedBy= and @Indexed and isn't @AuditMappedBy, add synthetic support. // If a property has mappedBy= and @Indexed and isn't @AuditMappedBy, add synthetic support.
@ -113,6 +111,18 @@ public class ClassesAuditingData {
); );
} }
} }
// HHH-9108
// Added support to handle nested property calculations for components.
// This is useful for AuditMappedBy inside an Embeddable that holds a collection of entities.
if ( propertyAuditingData instanceof ComponentAuditingData ) {
final ComponentAuditingData componentAuditingData = ( ComponentAuditingData) propertyAuditingData;
final Component component = (Component) property.getValue();
for ( String componentPropertyName : componentAuditingData.getNonSyntheticPropertyNames() ) {
final Property componentProperty = component.getProperty( componentPropertyName );
updateCalculatedProperty( entityName, componentProperty, componentPropertyName, componentAuditingData );
}
}
} }
private void setAuditMappedByInsertable( private void setAuditMappedByInsertable(

View File

@ -80,6 +80,13 @@ public class ClassAuditingData implements AuditedPropertiesHolder {
return properties.containsKey( propertyName ); return properties.containsKey( propertyName );
} }
public Iterable<String> getNonSyntheticPropertyNames() {
return properties.entrySet().stream()
.filter( e -> !e.getValue().isSyntheic() )
.map( Map.Entry::getKey )
.collect( Collectors.toList() );
}
public Iterable<PropertyAuditingData> getSyntheticProperties() { public Iterable<PropertyAuditingData> getSyntheticProperties() {
return properties.values().stream() return properties.values().stream()
.filter( p -> p.isSyntheic() ) .filter( p -> p.isSyntheic() )

View File

@ -8,6 +8,7 @@ package org.hibernate.envers.configuration.internal.metadata.reader;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import static org.hibernate.envers.internal.tools.Tools.newHashMap; import static org.hibernate.envers.internal.tools.Tools.newHashMap;
@ -16,6 +17,7 @@ import static org.hibernate.envers.internal.tools.Tools.newHashMap;
* *
* @author Adam Warski (adam at warski dot org) * @author Adam Warski (adam at warski dot org)
* @author Hern&aacut;n Chanfreau * @author Hern&aacut;n Chanfreau
* @author Chris Cranford
*/ */
public class ComponentAuditingData extends PropertyAuditingData implements AuditedPropertiesHolder { public class ComponentAuditingData extends PropertyAuditingData implements AuditedPropertiesHolder {
private final Map<String, PropertyAuditingData> properties; private final Map<String, PropertyAuditingData> properties;
@ -47,4 +49,12 @@ public class ComponentAuditingData extends PropertyAuditingData implements Audit
public Set<String> getPropertyNames() { public Set<String> getPropertyNames() {
return properties.keySet(); return properties.keySet();
} }
public Iterable<String> getNonSyntheticPropertyNames() {
return properties.entrySet().stream()
.filter( e -> !e.getValue().isSyntheic() )
.map( Map.Entry::getKey )
.collect( Collectors.toList() );
}
} }

View File

@ -162,14 +162,22 @@ public abstract class BaseEnversCollectionEventListener extends BaseEnversEventL
*/ */
private RelationDescription searchForRelationDescription(String entityName, String referencingPropertyName) { private RelationDescription searchForRelationDescription(String entityName, String referencingPropertyName) {
final EntityConfiguration configuration = getEnversService().getEntitiesConfigurations().get( entityName ); final EntityConfiguration configuration = getEnversService().getEntitiesConfigurations().get( entityName );
final RelationDescription rd = configuration.getRelationDescription( referencingPropertyName ); final String propertyName = sanitizeReferencingPropertyName( referencingPropertyName );
final RelationDescription rd = configuration.getRelationDescription( propertyName );
if ( rd == null && configuration.getParentEntityName() != null ) { if ( rd == null && configuration.getParentEntityName() != null ) {
return searchForRelationDescription( configuration.getParentEntityName(), referencingPropertyName ); return searchForRelationDescription( configuration.getParentEntityName(), propertyName );
} }
return rd; return rd;
} }
private String sanitizeReferencingPropertyName(String propertyName) {
if ( propertyName != null && propertyName.indexOf( '.' ) != -1 ) {
return propertyName.replaceAll( "\\.", "\\_" );
}
return propertyName;
}
private void generateFakeBidirecationalRelationWorkUnits( private void generateFakeBidirecationalRelationWorkUnits(
AuditProcess auditProcess, AuditProcess auditProcess,
PersistentCollection newColl, PersistentCollection newColl,