Skip to content

Commit 5baf43a

Browse files
committed
HHH-19897 Envers - check mapped-by side is audited for associations
1 parent ea00d69 commit 5baf43a

File tree

10 files changed

+176
-86
lines changed

10 files changed

+176
-86
lines changed

hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversMetadataBuildingContextImpl.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.hibernate.boot.spi.MetadataBuildingOptions;
1313
import org.hibernate.envers.boot.spi.EnversMetadataBuildingContext;
1414
import org.hibernate.envers.configuration.Configuration;
15+
import org.hibernate.envers.configuration.internal.ClassesAuditingData;
1516
import org.hibernate.envers.configuration.internal.MappingCollector;
1617
import org.hibernate.envers.configuration.internal.metadata.AuditEntityConfigurationRegistry;
1718
import org.hibernate.envers.configuration.internal.metadata.AuditEntityNameRegister;
@@ -33,6 +34,7 @@ public class EnversMetadataBuildingContextImpl implements EnversMetadataBuilding
3334
private final ObjectNameNormalizer objectNameNormalizer;
3435
private final AuditEntityNameRegister auditEntityNameRegistry;
3536
private final AuditEntityConfigurationRegistry auditEntityConfigurationRegistry;
37+
private final ClassesAuditingData classesAuditingData;
3638

3739
public EnversMetadataBuildingContextImpl(
3840
Configuration configuration,
@@ -45,6 +47,7 @@ public EnversMetadataBuildingContextImpl(
4547
this.mappingCollector = mappingCollector;
4648
this.auditEntityNameRegistry = new AuditEntityNameRegister();
4749
this.auditEntityConfigurationRegistry = new AuditEntityConfigurationRegistry();
50+
this.classesAuditingData = new ClassesAuditingData();
4851

4952
this.objectNameNormalizer = new ObjectNameNormalizer(this);
5053
}
@@ -118,4 +121,9 @@ public AuditEntityNameRegister getAuditEntityNameRegistry() {
118121
public AuditEntityConfigurationRegistry getAuditEntityConfigurationRegistry() {
119122
return auditEntityConfigurationRegistry;
120123
}
124+
125+
@Override
126+
public ClassesAuditingData getClassesAuditingData() {
127+
return classesAuditingData;
128+
}
121129
}

hibernate-envers/src/main/java/org/hibernate/envers/boot/spi/EnversMetadataBuildingContext.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import org.hibernate.boot.spi.MetadataBuildingContext;
88
import org.hibernate.envers.configuration.Configuration;
9+
import org.hibernate.envers.configuration.internal.ClassesAuditingData;
910
import org.hibernate.envers.configuration.internal.MappingCollector;
1011
import org.hibernate.envers.configuration.internal.metadata.AuditEntityConfigurationRegistry;
1112
import org.hibernate.envers.configuration.internal.metadata.AuditEntityNameRegister;
@@ -32,4 +33,6 @@ public interface EnversMetadataBuildingContext extends MetadataBuildingContext {
3233
AuditEntityNameRegister getAuditEntityNameRegistry();
3334

3435
AuditEntityConfigurationRegistry getAuditEntityConfigurationRegistry();
36+
37+
ClassesAuditingData getClassesAuditingData();
3538
}

hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/ClassesAuditingData.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ public Collection<ClassAuditingData> getAllClassAuditedData() {
6161
return persistentClassToAuditingData.values();
6262
}
6363

64+
public ClassAuditingData getClassAuditingData(String entityName) {
65+
return entityNameToAuditingData.get( entityName );
66+
}
67+
6468
/**
6569
* After all meta-data is read, updates calculated fields. This includes:
6670
* <ul>

hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/EntitiesConfigurator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public EntitiesConfigurations configure(EnversMetadataBuildingContext metadataBu
3838
final Iterator<PersistentClass> classes = GraphTopologicalSort.sort( new PersistentClassGraphDefiner( metadata ) )
3939
.iterator();
4040

41-
final ClassesAuditingData classesAuditingData = new ClassesAuditingData();
41+
final ClassesAuditingData classesAuditingData = metadataBuildingContext.getClassesAuditingData();
4242

4343
// Reading metadata from annotations
4444
final AnnotationsMetadataReader reader = new AnnotationsMetadataReader( metadataBuildingContext );

hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/CollectionMappedByResolver.java

Lines changed: 89 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,27 @@
44
*/
55
package org.hibernate.envers.configuration.internal.metadata;
66

7-
import java.lang.invoke.MethodHandles;
8-
import java.util.List;
9-
import java.util.Locale;
10-
import java.util.Objects;
11-
127
import org.hibernate.envers.boot.EnversMappingException;
8+
import org.hibernate.envers.boot.spi.EnversMetadataBuildingContext;
9+
import org.hibernate.envers.configuration.internal.metadata.reader.AuditedPropertiesHolder;
10+
import org.hibernate.envers.configuration.internal.metadata.reader.ClassAuditingData;
11+
import org.hibernate.envers.configuration.internal.metadata.reader.ComponentAuditingData;
1312
import org.hibernate.envers.configuration.internal.metadata.reader.PropertyAuditingData;
1413
import org.hibernate.envers.internal.EnversMessageLogger;
1514
import org.hibernate.mapping.Collection;
1615
import org.hibernate.mapping.Component;
1716
import org.hibernate.mapping.KeyValue;
18-
import org.hibernate.mapping.ManyToOne;
19-
import org.hibernate.mapping.OneToMany;
2017
import org.hibernate.mapping.PersistentClass;
2118
import org.hibernate.mapping.Property;
2219
import org.hibernate.mapping.Selectable;
2320
import org.hibernate.mapping.Table;
24-
2521
import org.jboss.logging.Logger;
2622

23+
import java.lang.invoke.MethodHandles;
24+
import java.util.List;
25+
import java.util.Locale;
26+
import java.util.Objects;
27+
2728
/**
2829
* Helper class that provides a way to resolve the {@code mappedBy} attribute for collections.
2930
*
@@ -37,26 +38,51 @@ public class CollectionMappedByResolver {
3738
CollectionMappedByResolver.class.getName()
3839
);
3940

40-
public static String resolveMappedBy(Collection collection, PropertyAuditingData propertyAuditingData) {
41-
final PersistentClass referencedClass = getReferenceCollectionClass( collection );
41+
public static String resolveMappedBy(
42+
String entityName,
43+
Collection collection,
44+
PropertyAuditingData propertyAuditingData,
45+
String referencedEntityName,
46+
EnversMetadataBuildingContext buildingContext) {
47+
final var referencedClass = getReferencedClass( referencedEntityName, buildingContext );
4248
final ResolverContext resolverContext = new ResolverContext( collection, propertyAuditingData );
43-
return getMappedBy( referencedClass, resolverContext );
49+
return getMappedBy( entityName, referencedClass, resolverContext, buildingContext );
4450
}
4551

46-
public static String resolveMappedBy(Table collectionTable, PersistentClass referencedClass, PropertyAuditingData propertyAuditingData) {
47-
return getMappedBy( referencedClass, new ResolverContext( collectionTable, propertyAuditingData ) );
52+
public static String resolveMappedBy(
53+
String entityName,
54+
Table collectionTable,
55+
PropertyAuditingData propertyAuditingData,
56+
String referencedEntityName,
57+
EnversMetadataBuildingContext buildingContext) {
58+
final var referencedClass = getReferencedClass( referencedEntityName, buildingContext );
59+
return getMappedBy(
60+
entityName,
61+
referencedClass,
62+
new ResolverContext( collectionTable, propertyAuditingData ),
63+
buildingContext
64+
);
4865
}
4966

50-
public static boolean isMappedByKey(Collection collection, String mappedBy) {
51-
final PersistentClass referencedClass = getReferenceCollectionClass( collection );
67+
public static boolean isMappedByKey(
68+
Collection collection,
69+
String mappedBy,
70+
String referencedEntityName,
71+
EnversMetadataBuildingContext buildingContext) {
72+
final PersistentClass referencedClass = getReferencedClass( referencedEntityName,
73+
buildingContext ).getPersistentClass();
5274
if ( referencedClass != null ) {
5375
final String keyMappedBy = searchMappedByKey( referencedClass, collection );
5476
return mappedBy.equals( keyMappedBy );
5577
}
5678
return false;
5779
}
5880

59-
private static String getMappedBy(PersistentClass referencedClass, ResolverContext resolverContext) {
81+
private static String getMappedBy(
82+
String entityName,
83+
ClassAuditingData referencedClass,
84+
ResolverContext resolverContext,
85+
EnversMetadataBuildingContext buildingContext) {
6086
// If there's an @AuditMappedBy specified, returning it directly.
6187
final String auditMappedBy = resolverContext.propertyAuditingData.getAuditMappedBy();
6288
if ( auditMappedBy != null ) {
@@ -70,74 +96,93 @@ private static String getMappedBy(PersistentClass referencedClass, ResolverConte
7096
LOG.debugf(
7197
"Going to search the mapped by attribute for %s in superclasses of entity: %s",
7298
resolverContext.propertyAuditingData.getName(),
73-
referencedClass.getClassName()
99+
referencedClass.getEntityName()
74100
);
75101

76-
PersistentClass tempClass = referencedClass;
77-
while ( mappedBy == null && tempClass.getSuperclass() != null ) {
78-
LOG.debugf( "Searching in superclass: %s", tempClass.getSuperclass().getClassName() );
79-
mappedBy = searchMappedBy( tempClass.getSuperclass(), resolverContext );
102+
PersistentClass tempClass = referencedClass.getPersistentClass().getSuperclass();
103+
while ( mappedBy == null && tempClass != null ) {
104+
final var superclassName = tempClass.getEntityName();
105+
LOG.debugf( "Searching in superclass: %s", superclassName );
106+
final var auditingData = buildingContext.getClassesAuditingData().getClassAuditingData( superclassName );
107+
mappedBy = searchMappedBy( auditingData, resolverContext );
80108
tempClass = tempClass.getSuperclass();
81109
}
82110
}
83111

84112
if ( mappedBy == null ) {
85113
throw new EnversMappingException(
86114
String.format(
87-
Locale.ENGLISH,
88-
"Unable to read mapped by attribute for %s in %s!",
115+
Locale.ROOT,
116+
"Could not resolve mapped by property for association [%s.%s] in the referenced entity [%s],"
117+
+ " please ensure that the association is audited on both sides.",
118+
entityName,
89119
resolverContext.propertyAuditingData.getName(),
90-
referencedClass.getClassName()
120+
referencedClass.getEntityName()
91121
)
92122
);
93123
}
94124

95125
return mappedBy;
96126
}
97127

98-
private static String searchMappedBy(PersistentClass persistentClass, ResolverContext resolverContext) {
128+
private static String searchMappedBy(ClassAuditingData referencedClass, ResolverContext resolverContext) {
99129
if ( resolverContext.getCollection() != null ) {
100-
return searchMappedBy( persistentClass, resolverContext.getCollection() );
130+
return searchMappedBy( referencedClass, resolverContext.getCollection() );
101131
}
102-
return searchMappedBy( persistentClass, resolverContext.getTable() );
132+
return searchMappedBy( referencedClass, resolverContext.getTable() );
103133
}
104134

105-
private static String searchMappedBy(PersistentClass referencedClass, Collection collectionValue) {
106-
final List<Property> assocClassProps = referencedClass.getProperties();
135+
private static String searchMappedBy(ClassAuditingData referencedClass, Collection collectionValue) {
136+
final var persistentClass = referencedClass.getPersistentClass();
137+
final List<Property> assocClassProps = referencedClass.getPersistentClass().getProperties();
107138
for ( Property property : assocClassProps ) {
108139
final List<Selectable> assocClassSelectables = property.getValue().getSelectables();
109140
final List<Selectable> collectionKeySelectables = collectionValue.getKey().getSelectables();
110141
if ( Objects.equals( assocClassSelectables, collectionKeySelectables ) ) {
111-
return property.getName();
142+
final var propertyName = property.getName();
143+
// We need to check if the property is audited as well
144+
return referencedClass.contains( propertyName ) ? propertyName : null;
112145
}
113146
}
114147
// HHH-7625
115148
// Support ToOne relations with mappedBy that point to an @IdClass key property.
116-
return searchMappedByKey( referencedClass, collectionValue );
149+
return searchMappedByKey( persistentClass, collectionValue );
117150
}
118151

119-
private static String searchMappedBy(PersistentClass referencedClass, Table collectionTable) {
120-
return searchMappedBy( referencedClass.getProperties(), collectionTable );
152+
private static String searchMappedBy(ClassAuditingData referencedClass, Table collectionTable) {
153+
return searchMappedBy( referencedClass, referencedClass.getPersistentClass().getProperties(), collectionTable );
121154
}
122155

123-
private static String searchMappedBy(List<Property> properties, Table collectionTable) {
156+
private static String searchMappedBy(AuditedPropertiesHolder propertiesHolder, List<Property> properties, Table collectionTable) {
124157
for ( Property property : properties ) {
125158
if ( property.getValue() instanceof Collection ) {
126159
// The equality is intentional. We want to find a collection property with the same collection table.
127160
//noinspection ObjectEquality
128-
if ( ( (Collection) property.getValue() ).getCollectionTable() == collectionTable ) {
129-
return property.getName();
161+
if ( ((Collection) property.getValue()).getCollectionTable() == collectionTable ) {
162+
final var propertyName = property.getName();
163+
// We need to check if the property is audited as well
164+
return propertiesHolder.contains( propertyName ) ? propertyName : null;
130165
}
131166
}
132-
else if ( property.getValue() instanceof Component ) {
167+
else if ( property.getValue() instanceof Component component ) {
133168
// HHH-12240
134169
// Should we find an embeddable, we should traverse it as well to see if the collection table
135170
// happens to be an attribute inside the embeddable rather than directly on the entity.
136-
final Component component = (Component) property.getValue();
137-
138-
final String mappedBy = searchMappedBy( component.getProperties(), collectionTable );
139-
if ( mappedBy != null ) {
140-
return property.getName() + "_" + mappedBy;
171+
final var componentName = property.getName();
172+
final var componentData = propertiesHolder.getPropertyAuditingData( componentName );
173+
if ( componentData == null ) {
174+
// If the component is not audited, no need to check sub-properties
175+
return null;
176+
}
177+
else {
178+
final String mappedBy = searchMappedBy(
179+
(ComponentAuditingData) componentData,
180+
component.getProperties(),
181+
collectionTable
182+
);
183+
if ( mappedBy != null ) {
184+
return property.getName() + "_" + mappedBy;
185+
}
141186
}
142187
}
143188
}
@@ -161,18 +206,8 @@ private static String searchMappedByKey(PersistentClass referencedClass, Collect
161206
return null;
162207
}
163208

164-
private static PersistentClass getReferenceCollectionClass(Collection collectionValue) {
165-
PersistentClass referencedClass = null;
166-
if ( collectionValue.getElement() instanceof OneToMany ) {
167-
final OneToMany oneToManyValue = (OneToMany) collectionValue.getElement();
168-
referencedClass = oneToManyValue.getAssociatedClass();
169-
}
170-
else if ( collectionValue.getElement() instanceof ManyToOne ) {
171-
// Case for bidirectional relation with @JoinTable on the owning @ManyToOne side.
172-
final ManyToOne manyToOneValue = (ManyToOne) collectionValue.getElement();
173-
referencedClass = manyToOneValue.getMetadata().getEntityBinding( manyToOneValue.getReferencedEntityName() );
174-
}
175-
return referencedClass;
209+
private static ClassAuditingData getReferencedClass(String className, EnversMetadataBuildingContext buildingContext) {
210+
return buildingContext.getClassesAuditingData().getClassAuditingData( className );
176211
}
177212

178213
private static class ResolverContext {

hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/JoinColumnCollectionMetadataGenerator.java

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,27 @@ public JoinColumnCollectionMetadataGenerator(
5454

5555
@Override
5656
public void addCollection(CollectionMetadataContext context) {
57+
final var referencingEntityName = context.getReferencingEntityName();
5758
LOG.debugf(
5859
"Adding audit mapping for property %s.%s: one-to-many collection, using a join column on the referenced entity",
59-
context.getReferencingEntityName(),
60+
referencingEntityName,
6061
context.getPropertyName()
6162
);
6263

6364
final Collection collection = context.getCollection();
6465
final PropertyAuditingData propertyAuditingData = context.getPropertyAuditingData();
65-
final String mappedBy = CollectionMappedByResolver.resolveMappedBy( collection, propertyAuditingData );
66+
final var referencedEntityName = context.getReferencedEntityName();
67+
final String mappedBy = CollectionMappedByResolver.resolveMappedBy(
68+
referencingEntityName,
69+
collection,
70+
propertyAuditingData,
71+
referencedEntityName,
72+
getMetadataBuildingContext()
73+
);
6674

6775
final IdMappingData referencedIdMapping = getReferencedIdMappingData(
68-
context.getReferencingEntityName(),
69-
context.getReferencedEntityName(),
76+
referencingEntityName,
77+
referencedEntityName,
7078
propertyAuditingData,
7179
false
7280
);
@@ -78,15 +86,15 @@ public void addCollection(CollectionMetadataContext context) {
7886
final MiddleIdData referencingIdData = createMiddleIdData(
7987
referencingIdMapping,
8088
mappedBy + "_",
81-
context.getReferencingEntityName()
89+
referencingEntityName
8290
);
8391

8492
// And for the referenced side. The prefixed mapper won't be used (as this collection isn't persisted
8593
// in a join table, so the prefix value is arbitrary).
8694
final MiddleIdData referencedIdData = createMiddleIdData(
8795
referencedIdMapping,
8896
null,
89-
context.getReferencedEntityName()
97+
referencedEntityName
9098
);
9199

92100
// Generating the element mapping.
@@ -102,18 +110,18 @@ public void addCollection(CollectionMetadataContext context) {
102110
final RelationQueryGenerator queryGenerator = new OneAuditEntityQueryGenerator(
103111
getMetadataBuildingContext().getConfiguration(),
104112
referencingIdData,
105-
context.getReferencedEntityName(),
113+
referencedEntityName,
106114
referencedIdData,
107115
context.getCollection().getElement() instanceof ComponentType,
108116
mappedBy,
109-
CollectionMappedByResolver.isMappedByKey( collection, mappedBy ),
117+
CollectionMappedByResolver.isMappedByKey( collection, mappedBy, referencedEntityName, getMetadataBuildingContext() ),
110118
getOrderByCollectionRole( collection, collection.getOrderBy() )
111119
);
112120

113121
// Creating common mapper data.
114122
final CommonCollectionMapperData commonCollectionMapperData = createCommonCollectionMapperData(
115123
context,
116-
context.getReferencedEntityName(),
124+
referencedEntityName,
117125
referencingIdData,
118126
queryGenerator
119127
);
@@ -126,7 +134,7 @@ public void addCollection(CollectionMetadataContext context) {
126134
final String auditMappedBy = getAddOneToManyAttachedAuditMappedBy( context );
127135

128136
fakeBidirectionalRelationMapper = getBidirectionalRelationMapper(
129-
context.getReferencingEntityName(),
137+
referencingEntityName,
130138
referencingIdMapping,
131139
auditMappedBy
132140
);
@@ -153,7 +161,7 @@ public void addCollection(CollectionMetadataContext context) {
153161
referencingEntityConfiguration.addToManyNotOwningRelation(
154162
context.getPropertyName(),
155163
mappedBy,
156-
context.getReferencedEntityName(),
164+
referencedEntityName,
157165
referencingIdData.getPrefixedMapper(),
158166
fakeBidirectionalRelationMapper,
159167
fakeBidirectionalRelationIndexMapper,

0 commit comments

Comments
 (0)