org.hibernate.envers.event.spi.AbstractEnversCollectionEventListener Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-core Show documentation
Show all versions of hibernate-core Show documentation
Hibernate's core ORM functionality
/*
* 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 .
*/
package org.hibernate.envers.event.spi;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.spi.CollectionEntry;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.boot.AuditService;
import org.hibernate.envers.internal.entities.EntityConfiguration;
import org.hibernate.envers.internal.entities.RelationDescription;
import org.hibernate.envers.internal.entities.RelationType;
import org.hibernate.envers.internal.entities.mapper.PersistentCollectionChangeData;
import org.hibernate.envers.internal.entities.mapper.id.IdMapper;
import org.hibernate.envers.internal.synchronization.AuditProcess;
import org.hibernate.envers.internal.synchronization.work.AuditWorkUnit;
import org.hibernate.envers.internal.synchronization.work.CollectionChangeWorkUnit;
import org.hibernate.envers.internal.synchronization.work.FakeBidirectionalRelationWorkUnit;
import org.hibernate.envers.internal.synchronization.work.PersistentCollectionChangeWorkUnit;
import org.hibernate.event.spi.AbstractCollectionEvent;
import org.hibernate.metamodel.model.domain.spi.CollectionElement;
/**
* Abstract class for Envers' collection event related listeners
*
* @author Adam Warski (adam at warski dot org)
* @author HernпїЅn Chanfreau
* @author Steve Ebersole
* @author Michal Skowronek (mskowr at o2 dot pl)
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
* @author Chris Cranford
*/
public abstract class AbstractEnversCollectionEventListener extends AbstractEnversEventListener {
protected AbstractEnversCollectionEventListener(AuditService auditService) {
super( auditService );
}
protected final CollectionEntry getCollectionEntry(AbstractCollectionEvent event) {
return event.getSession().getPersistenceContext().getCollectionEntry( event.getCollection() );
}
protected final void onCollectionAction(
AbstractCollectionEvent event,
PersistentCollection newColl,
Serializable oldColl,
CollectionEntry collectionEntry) {
if ( shouldGenerateRevision( event ) ) {
checkIfTransactionInProgress( event.getSession() );
final AuditProcess auditProcess = getAuditService().getAuditProcess( event.getSession() );
final String entityName = event.getAffectedOwnerEntityName();
final String referencingPropertyName = resolveReferencingPropertyName( collectionEntry );
// Checking if this is not a "fake" many-to-one bidirectional relation. The relation description may be
// null in case of collections of non-entities.
final RelationDescription rd = searchForRelationDescription( entityName, referencingPropertyName );
if ( rd != null && rd.getMappedByPropertyName() != null ) {
generateFakeBidirecationalRelationWorkUnits(
auditProcess,
newColl,
oldColl,
entityName,
referencingPropertyName,
event,
rd
);
}
else {
final PersistentCollectionChangeWorkUnit workUnit = new PersistentCollectionChangeWorkUnit(
event.getSession(),
entityName,
getAuditService(),
newColl,
collectionEntry,
oldColl,
event.getAffectedOwnerIdOrNull(),
referencingPropertyName
);
auditProcess.addWorkUnit( workUnit );
if ( workUnit.containsWork() ) {
// There are some changes: a revision needs also be generated for the collection owner
auditProcess.addWorkUnit(
new CollectionChangeWorkUnit(
event.getSession(),
event.getAffectedOwnerEntityName(),
referencingPropertyName,
getAuditService(),
event.getAffectedOwnerIdOrNull(),
event.getAffectedOwnerOrNull()
)
);
generateBidirectionalCollectionChangeWorkUnits( auditProcess, event, workUnit, rd );
}
}
}
}
protected final void onCollectionActionInversed(
AbstractCollectionEvent event,
PersistentCollection newColl,
Serializable oldColl,
CollectionEntry collectionEntry) {
if ( shouldGenerateRevision( event ) ) {
final String entityName = event.getAffectedOwnerEntityName();
final String referencingPropertyName = resolveReferencingPropertyName( collectionEntry );
final RelationDescription rd = searchForRelationDescription( entityName, referencingPropertyName );
if ( rd != null ) {
if ( rd.getRelationType().equals( RelationType.TO_MANY_NOT_OWNING ) && rd.isIndexed() ) {
onCollectionAction( event, newColl, oldColl, collectionEntry );
}
}
}
}
/**
* Forces persistent collection initialization.
*
* @param event Collection event.
*
* @return Stored snapshot.
*/
protected Serializable initializeCollection(AbstractCollectionEvent event) {
event.getCollection().forceInitialization();
return event.getCollection().getStoredSnapshot();
}
/**
* Checks whether modification of not-owned relation field triggers new revision and owner entity is versioned.
*
* @param event Collection event.
*
* @return {@code true} if revision based on given event should be generated, {@code false} otherwise.
*/
protected boolean shouldGenerateRevision(AbstractCollectionEvent event) {
final String entityName = event.getAffectedOwnerEntityName();
if ( getAuditService().getEntityBindings().isVersioned( entityName ) ) {
final CollectionEntry collectionEntry = getCollectionEntry( event );
if ( isInverse( collectionEntry ) || isOneToMany( collectionEntry ) ) {
return getAuditService().getOptions().isRevisionOnCollectionChangeEnabled();
}
return true;
}
// if this entity is not audited, we do not generate a revision.
return false;
}
/**
* Looks up a relation description corresponding to the given property in the given entity. If no description is
* found in the given entity, the parent entity is checked (so that inherited relations work).
*
* @param entityName Name of the entity, in which to start looking.
* @param referencingPropertyName The name of the property.
*
* @return A found relation description corresponding to the given entity or {@code null}, if no description can
* be found.
*/
private RelationDescription searchForRelationDescription(String entityName, String referencingPropertyName) {
final EntityConfiguration configuration = getAuditService().getEntityBindings().get( entityName );
final String propertyName = sanitizeReferencingPropertyName( referencingPropertyName );
final RelationDescription rd = configuration.getRelationDescription( propertyName );
if ( rd == null && configuration.getParentEntityName() != null ) {
return searchForRelationDescription( configuration.getParentEntityName(), propertyName );
}
return rd;
}
private String sanitizeReferencingPropertyName(String propertyName) {
if ( propertyName != null && propertyName.indexOf( '.' ) != -1 ) {
return propertyName.replaceAll( "\\.", "\\_" );
}
return propertyName;
}
private void generateFakeBidirecationalRelationWorkUnits(
AuditProcess auditProcess,
PersistentCollection newColl,
Serializable oldColl,
String collectionEntityName,
String referencingPropertyName,
AbstractCollectionEvent event,
RelationDescription rd) {
// First computing the relation changes
final List collectionChanges = getAuditService()
.getEntityBindings()
.get( collectionEntityName )
.getPropertyMapper()
.mapCollectionChanges(
event.getSession(),
referencingPropertyName,
newColl,
oldColl,
event.getAffectedOwnerIdOrNull()
);
// Getting the id mapper for the related entity, as the work units generated will correspond to the related
// entities.
final String relatedEntityName = rd.getToEntityName();
final IdMapper relatedIdMapper = getAuditService().getEntityBindings().get( relatedEntityName ).getIdMapper();
// For each collection change, generating the bidirectional work unit.
for ( PersistentCollectionChangeData changeData : collectionChanges ) {
final Object relatedObj = changeData.getChangedElement();
final Serializable relatedId = (Serializable) relatedIdMapper.mapToIdFromEntity( relatedObj );
final RevisionType revType = (RevisionType) changeData.getData().get(
getAuditService().getOptions().getRevisionTypePropName()
);
// This can be different from relatedEntityName, in case of inheritance (the real entity may be a subclass
// of relatedEntityName).
final String realRelatedEntityName = event.getSession().bestGuessEntityName( relatedObj );
// By default, the nested work unit is a collection change work unit.
final AuditWorkUnit nestedWorkUnit = new CollectionChangeWorkUnit(
event.getSession(),
realRelatedEntityName,
rd.getMappedByPropertyName(),
getAuditService(),
relatedId,
relatedObj
);
auditProcess.addWorkUnit(
new FakeBidirectionalRelationWorkUnit(
event.getSession(),
realRelatedEntityName,
getAuditService(),
relatedId,
referencingPropertyName,
event.getAffectedOwnerOrNull(),
rd,
revType,
changeData.getChangedElementIndex(),
nestedWorkUnit
)
);
}
// We also have to generate a collection change work unit for the owning entity.
auditProcess.addWorkUnit(
new CollectionChangeWorkUnit(
event.getSession(),
collectionEntityName,
referencingPropertyName,
getAuditService(),
event.getAffectedOwnerIdOrNull(),
event.getAffectedOwnerOrNull()
)
);
}
private void generateBidirectionalCollectionChangeWorkUnits(
AuditProcess auditProcess,
AbstractCollectionEvent event,
PersistentCollectionChangeWorkUnit workUnit,
RelationDescription rd) {
// Checking if this is enabled in configuration ...
if ( !getAuditService().getOptions().isRevisionOnCollectionChangeEnabled() ) {
return;
}
// Checking if this is not a bidirectional relation - then, a revision needs also be generated for
// the other side of the relation.
// relDesc can be null if this is a collection of simple values (not a relation).
if ( rd != null && rd.isBidirectional() ) {
final String relatedEntityName = rd.getToEntityName();
final IdMapper relatedIdMapper = getAuditService().getEntityBindings()
.get( relatedEntityName )
.getIdMapper();
final Set toPropertyNames = getAuditService().getEntityBindings()
.getToPropertyNames(
event.getAffectedOwnerEntityName(),
rd.getFromPropertyName(),
relatedEntityName
);
final String toPropertyName = toPropertyNames.iterator().next();
for ( PersistentCollectionChangeData changeData : workUnit.getCollectionChanges() ) {
final Object relatedObj = changeData.getChangedElement();
final Serializable relatedId = (Serializable) relatedIdMapper.mapToIdFromEntity( relatedObj );
auditProcess.addWorkUnit(
new CollectionChangeWorkUnit(
event.getSession(),
event.getSession().bestGuessEntityName( relatedObj ),
toPropertyName,
getAuditService(),
relatedId,
relatedObj
)
);
}
}
}
private String resolveReferencingPropertyName(CollectionEntry collectionEntry) {
final String ownerEntityName = collectionEntry.getLoadedCollectionDescriptor()
.getContainer()
.getNavigableName();
return collectionEntry.getNavigableRole().getFullPath().substring( ownerEntityName.length() + 1 );
}
private boolean isInverse(CollectionEntry collectionEntry) {
return collectionEntry.getLoadedCollectionDescriptor().isInverse();
}
private boolean isOneToMany(CollectionEntry collectionEntry) {
return collectionEntry.getLoadedCollectionDescriptor()
.getElementDescriptor()
.getClassification()
.equals( CollectionElement.ElementClassification.ONE_TO_MANY );
}
}