Skip to content

Commit

Permalink
HHH-7940 - Fix NullPointerException when using IndexColumn/OrderColum…
Browse files Browse the repository at this point in the history
…n without AuditMappedBy.
  • Loading branch information
Naros committed Dec 19, 2016
1 parent 94f2401 commit 8db194e
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 39 deletions.
Expand Up @@ -12,18 +12,24 @@
import java.util.Map;

import org.hibernate.MappingException;
import org.hibernate.envers.ModificationStore;
import org.hibernate.envers.RelationTargetAuditMode;
import org.hibernate.envers.configuration.internal.metadata.reader.ClassAuditingData;
import org.hibernate.envers.configuration.internal.metadata.reader.PropertyAuditingData;
import org.hibernate.envers.internal.EnversMessageLogger;
import org.hibernate.envers.internal.tools.MappingTools;
import org.hibernate.mapping.List;
import org.hibernate.mapping.PersistentClass;

import org.hibernate.mapping.Property;
import org.hibernate.mapping.Value;
import org.jboss.logging.Logger;

/**
* A helper class holding auditing meta-data for all persistent classes.
*
* @author Adam Warski (adam at warski dot org)
* @author Chris Cranford
*/
public class ClassesAuditingData {
private static final EnversMessageLogger LOG = Logger.getMessageLogger(
Expand Down Expand Up @@ -65,32 +71,87 @@ public ClassAuditingData getClassAuditingData(String entityName) {
* After all meta-data is read, updates calculated fields. This includes:
* <ul>
* <li>setting {@code forceInsertable} to {@code true} for properties specified by {@code @AuditMappedBy}</li>
* <li>adding {@code synthetic} properties to mappedBy relations which have {@code IndexColumn} or {@code OrderColumn}.</li>
* </ul>
*/
public void updateCalculatedFields() {
for ( Map.Entry<PersistentClass, ClassAuditingData> classAuditingDataEntry : persistentClassToAuditingData.entrySet() ) {
final PersistentClass pc = classAuditingDataEntry.getKey();
final ClassAuditingData classAuditingData = classAuditingDataEntry.getValue();
for ( String propertyName : classAuditingData.getPropertyNames() ) {
final PropertyAuditingData propertyAuditingData = classAuditingData.getPropertyAuditingData( propertyName );
updateCalculatedProperty( pc, classAuditingData, propertyName );
}
}
}

private void updateCalculatedProperty(
PersistentClass pc,
ClassAuditingData classAuditingData,
String propertyName) {

final PropertyAuditingData propertyAuditingData = classAuditingData.getPropertyAuditingData( propertyName );

final boolean isAuditMappedBy = propertyAuditingData.getAuditMappedBy() != null;
final boolean isRelationMappedBy = propertyAuditingData.getRelationMappedBy() != null;

if ( isAuditMappedBy || isRelationMappedBy ) {
final Property property = pc.getProperty( propertyName );
final String referencedEntityName = MappingTools.getReferencedEntityName( property.getValue() );

final ClassAuditingData referencedAuditData = entityNameToAuditingData.get( referencedEntityName );

if ( isAuditMappedBy ) {
// If a property had the @AuditMappedBy annotation, setting the referenced fields to be always insertable.
if ( propertyAuditingData.getAuditMappedBy() != null ) {
final String referencedEntityName = MappingTools.getReferencedEntityName(
pc.getProperty( propertyName ).getValue()
);

final ClassAuditingData referencedClassAuditingData = entityNameToAuditingData.get( referencedEntityName );

forcePropertyInsertable(
referencedClassAuditingData, propertyAuditingData.getAuditMappedBy(),
pc.getEntityName(), referencedEntityName
);

forcePropertyInsertable(
referencedClassAuditingData, propertyAuditingData.getPositionMappedBy(),
pc.getEntityName(), referencedEntityName
);
}
setAuditMappedByInsertable( referencedEntityName, pc.getEntityName(), referencedAuditData, propertyAuditingData );
}
else if ( isRelationMappedBy && ( property.getValue() instanceof List ) ) {
// If a property has mappedBy= and @Indexed and isn't @AuditMappedBy, add synthetic support.
addSyntheticIndexProperty(
(List) property.getValue(),
property.getPropertyAccessorName(),
referencedAuditData
);
}
}
}

private void setAuditMappedByInsertable(
String referencedEntityName,
String entityName,
ClassAuditingData referencedAuditData,
PropertyAuditingData propertyAuditingData) {
forcePropertyInsertable(
referencedAuditData,
propertyAuditingData.getAuditMappedBy(),
entityName,
referencedEntityName
);

forcePropertyInsertable(
referencedAuditData,
propertyAuditingData.getPositionMappedBy(),
entityName,
referencedEntityName
);
}

private void addSyntheticIndexProperty(List value, String propertyAccessorName, ClassAuditingData classAuditingData) {
final Value indexValue = value.getIndex();
if ( indexValue != null && indexValue.getColumnIterator().hasNext() ) {
final String indexColumnName = indexValue.getColumnIterator().next().getText();
if ( indexColumnName != null ) {
final PropertyAuditingData auditingData = new PropertyAuditingData(
indexColumnName,
propertyAccessorName,
ModificationStore.FULL,
RelationTargetAuditMode.AUDITED,
null,
null,
false,
true,
indexValue
);
classAuditingData.addPropertyAuditingData( indexColumnName, auditingData );
}
}
}
Expand Down
Expand Up @@ -694,6 +694,9 @@ public void generateFirstPass(
createJoins( pc, classMapping, auditingData );
addJoins( pc, propertyMapper, auditingData, pc.getEntityName(), xmlMappingData, true );

// HHH-7940 - New synthetic property support for @IndexColumn/@OrderColumn dynamic properties
addSynthetics( classMapping, auditingData, propertyMapper, xmlMappingData, pc.getEntityName(), true );

// Storing the generated configuration
final EntityConfiguration entityCfg = new EntityConfiguration(
auditEntityName,
Expand All @@ -705,6 +708,28 @@ public void generateFirstPass(
entitiesConfigurations.put( pc.getEntityName(), entityCfg );
}

private void addSynthetics(
Element classMapping,
ClassAuditingData auditingData,
CompositeMapperBuilder currentMapper,
EntityXmlMappingData xmlMappingData,
String entityName,
boolean firstPass) {
for ( PropertyAuditingData propertyAuditingData : auditingData.getSyntheticProperties() ) {
addValue(
classMapping,
propertyAuditingData.getValue(),
currentMapper,
entityName,
xmlMappingData,
propertyAuditingData,
true,
firstPass,
false
);
}
}

@SuppressWarnings({"unchecked"})
public void generateSecondPass(
PersistentClass pc,
Expand Down
Expand Up @@ -157,7 +157,7 @@ void addCollection() {

if ( oneToManyAttachedType && (inverseOneToMany || fakeOneToManyBidirectional || owningManyToOneWithJoinTableBidirectional) ) {
// A one-to-many relation mapped using @ManyToOne and @OneToMany(mappedBy="...")
addOneToManyAttached( fakeOneToManyBidirectional );
addOneToManyAttached( fakeOneToManyBidirectional );
}
else {
// All other kinds of relations require a middle (join) table.
Expand All @@ -183,6 +183,10 @@ private void addOneToManyAttached(boolean fakeOneToManyBidirectional) {
propertyName
);

// check whether the property has an @IndexColumn or @OrderColumn because its part of an
// IndexedCollection mapping type.
final boolean indexed = ( propertyValue instanceof IndexedCollection ) && ( (IndexedCollection) propertyValue ).getIndex() != null;

final String mappedBy = getMappedBy( propertyValue );

final IdMappingData referencedIdMapping = mainGenerator.getReferencedIdMappingData(
Expand Down Expand Up @@ -239,10 +243,16 @@ private void addOneToManyAttached(boolean fakeOneToManyBidirectional) {

PropertyMapper fakeBidirectionalRelationMapper;
PropertyMapper fakeBidirectionalRelationIndexMapper;
if ( fakeOneToManyBidirectional ) {
if ( fakeOneToManyBidirectional || indexed ) {
// In case of a fake many-to-one bidirectional relation, we have to generate a mapper which maps
// the mapped-by property name to the id of the related entity (which is the owner of the collection).
final String auditMappedBy = propertyAuditingData.getAuditMappedBy();
final String auditMappedBy;
if ( fakeOneToManyBidirectional ) {
auditMappedBy = propertyAuditingData.getAuditMappedBy();
}
else {
auditMappedBy = propertyValue.getMappedByProperty();
}

// Creating a prefixed relation mapper.
final IdMapper relMapper = referencingIdMapping.getIdMapper().prefixMappedProperties(
Expand All @@ -257,9 +267,20 @@ private void addOneToManyAttached(boolean fakeOneToManyBidirectional) {
referencingEntityName, false
);

final String positionMappedBy;
if ( fakeOneToManyBidirectional ) {
positionMappedBy = propertyAuditingData.getPositionMappedBy();
}
else if ( indexed ) {
final Value indexValue = ( (IndexedCollection) propertyValue ).getIndex();
positionMappedBy = indexValue.getColumnIterator().next().getText();
}
else {
positionMappedBy = null;
}

// Checking if there's an index defined. If so, adding a mapper for it.
if ( propertyAuditingData.getPositionMappedBy() != null ) {
final String positionMappedBy = propertyAuditingData.getPositionMappedBy();
if ( positionMappedBy != null ) {
fakeBidirectionalRelationIndexMapper = new SinglePropertyMapper(
new PropertyData(
positionMappedBy,
Expand Down Expand Up @@ -294,7 +315,8 @@ private void addOneToManyAttached(boolean fakeOneToManyBidirectional) {
referencedEntityName,
referencingIdData.getPrefixedMapper(),
fakeBidirectionalRelationMapper,
fakeBidirectionalRelationIndexMapper
fakeBidirectionalRelationIndexMapper,
indexed
);
}

Expand Down
Expand Up @@ -7,6 +7,7 @@
package org.hibernate.envers.configuration.internal.metadata.reader;

import java.util.Map;
import java.util.stream.Collectors;

import org.hibernate.envers.AuditTable;

Expand All @@ -16,6 +17,7 @@
* @author Adam Warski (adam at warski dot org)
* @author Sebastian Komander
* @author Hern&aacut;n Chanfreau
* @author Chris Cranford
*/
public class ClassAuditingData implements AuditedPropertiesHolder {
private final Map<String, PropertyAuditingData> properties;
Expand Down Expand Up @@ -77,4 +79,10 @@ public boolean isAudited() {
public boolean contains(String propertyName) {
return properties.containsKey( propertyName );
}

public Iterable<PropertyAuditingData> getSyntheticProperties() {
return properties.values().stream()
.filter( p -> p.isSyntheic() )
.collect( Collectors.toList() );
}
}
Expand Up @@ -15,10 +15,12 @@
import org.hibernate.envers.ModificationStore;
import org.hibernate.envers.RelationTargetAuditMode;
import org.hibernate.envers.internal.entities.PropertyData;
import org.hibernate.mapping.Value;

/**
* @author Adam Warski (adam at warski dot org)
* @author Michal Skowronek (mskowr at o2 dot pl)
* @author Chris Cranford
*/
public class PropertyAuditingData {
private String name;
Expand All @@ -35,6 +37,10 @@ public class PropertyAuditingData {
private boolean forceInsertable;
private boolean usingModifiedFlag;
private String modifiedFlagName;
private Value value;
// Synthetic properties are ones which are not part of the actual java model.
// They're properties used for bookkeeping by Hibernate
private boolean syntheic;

public PropertyAuditingData() {
}
Expand All @@ -44,6 +50,29 @@ public PropertyAuditingData(
RelationTargetAuditMode relationTargetAuditMode,
String auditMappedBy, String positionMappedBy,
boolean forceInsertable) {
this(
name,
accessType,
store,
relationTargetAuditMode,
auditMappedBy,
positionMappedBy,
forceInsertable,
false,
null
);
}

public PropertyAuditingData(
String name,
String accessType,
ModificationStore store,
RelationTargetAuditMode relationTargetAuditMode,
String auditMappedBy,
String positionMappedBy,
boolean forceInsertable,
boolean syntheic,
Value value) {
this.name = name;
this.beanName = name;
this.accessType = accessType;
Expand All @@ -52,6 +81,8 @@ public PropertyAuditingData(
this.auditMappedBy = auditMappedBy;
this.positionMappedBy = positionMappedBy;
this.forceInsertable = forceInsertable;
this.syntheic = syntheic;
this.value = value;
}

public String getName() {
Expand Down Expand Up @@ -104,8 +135,13 @@ public void setAccessType(String accessType) {

public PropertyData getPropertyData() {
return new PropertyData(
name, beanName, accessType, store,
usingModifiedFlag, modifiedFlagName
name,
beanName,
accessType,
store,
usingModifiedFlag,
modifiedFlagName,
syntheic
);
}

Expand Down Expand Up @@ -203,4 +239,11 @@ public void setRelationTargetAuditMode(RelationTargetAuditMode relationTargetAud
this.relationTargetAuditMode = relationTargetAuditMode;
}

public boolean isSyntheic() {
return syntheic;
}

public Value getValue() {
return value;
}
}

0 comments on commit 8db194e

Please sign in to comment.