package org.hibernate.envers.synchronization.work;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.configuration.AuditConfiguration;
import org.hibernate.envers.entities.RelationDescription;
/**
* A work unit that handles "fake" bidirectional one-to-many relations (mapped with {@code @OneToMany+@JoinColumn} and
* {@code @ManyToOne+@Column(insertable=false, updatable=false)}.
* @author Adam Warski (adam at warski dot org)
*/
public class FakeBidirectionalRelationWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit {
private final Map<String, FakeRelationChange> fakeRelationChanges;
/*
* The work unit responsible for generating the "raw" entity data to be saved.
*/
private final AuditWorkUnit nestedWorkUnit;
public FakeBidirectionalRelationWorkUnit(SessionImplementor sessionImplementor, String entityName,
AuditConfiguration verCfg, Serializable id,
String referencingPropertyName, Object owningEntity,
RelationDescription rd, RevisionType revisionType,
Object index,
AuditWorkUnit nestedWorkUnit) {
super(sessionImplementor, entityName, verCfg, id, revisionType);
this.nestedWorkUnit = nestedWorkUnit;
// Adding the change for the relation.
fakeRelationChanges = new HashMap<String, FakeRelationChange>();
fakeRelationChanges.put(referencingPropertyName, new FakeRelationChange(owningEntity, rd, revisionType, index));
}
public FakeBidirectionalRelationWorkUnit(FakeBidirectionalRelationWorkUnit original,
Map<String, FakeRelationChange> fakeRelationChanges,
AuditWorkUnit nestedWorkUnit) {
super(original.sessionImplementor, original.entityName, original.verCfg, original.id, original.revisionType);
this.fakeRelationChanges = fakeRelationChanges;
this.nestedWorkUnit = nestedWorkUnit;
}
public FakeBidirectionalRelationWorkUnit(FakeBidirectionalRelationWorkUnit original, AuditWorkUnit nestedWorkUnit) {
super(original.sessionImplementor, original.entityName, original.verCfg, original.id, original.revisionType);
this.nestedWorkUnit = nestedWorkUnit;
fakeRelationChanges = new HashMap<String, FakeRelationChange>(original.getFakeRelationChanges());
}
public AuditWorkUnit getNestedWorkUnit() {
return nestedWorkUnit;
}
public Map<String, FakeRelationChange> getFakeRelationChanges() {
return fakeRelationChanges;
}
public boolean containsWork() {
return true;
}
public Map<String, Object> generateData(Object revisionData) {
// Generating data with the nested work unit. This data contains all data except the fake relation.
// Making a defensive copy not to modify the data held by the nested work unit.
Map<String, Object> nestedData = new HashMap<String, Object>(nestedWorkUnit.generateData(revisionData));
// Now adding data for all fake relations.
for (FakeRelationChange fakeRelationChange : fakeRelationChanges.values()) {
fakeRelationChange.generateData(sessionImplementor, nestedData);
}
return nestedData;
}
public AuditWorkUnit merge(AddWorkUnit second) {
return merge(this, nestedWorkUnit, second);
}
public AuditWorkUnit merge(ModWorkUnit second) {
return merge(this, nestedWorkUnit, second);
}
public AuditWorkUnit merge(DelWorkUnit second) {
return second;
}
public AuditWorkUnit merge(CollectionChangeWorkUnit second) {
return this;
}
public AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second) {
// First merging the nested work units.
AuditWorkUnit mergedNested = second.getNestedWorkUnit().dispatch(nestedWorkUnit);
// Now merging the fake relation changes from both work units.
Map<String, FakeRelationChange> secondFakeRelationChanges = second.getFakeRelationChanges();
Map<String, FakeRelationChange> mergedFakeRelationChanges = new HashMap<String, FakeRelationChange>();
Set<String> allPropertyNames = new HashSet<String>(fakeRelationChanges.keySet());
allPropertyNames.addAll(secondFakeRelationChanges.keySet());
for (String propertyName : allPropertyNames) {
mergedFakeRelationChanges.put(propertyName,
FakeRelationChange.merge(
fakeRelationChanges.get(propertyName),
secondFakeRelationChanges.get(propertyName)));
}
return new FakeBidirectionalRelationWorkUnit(this, mergedFakeRelationChanges, mergedNested);
}
public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
return first.merge(this);
}
public static AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit frwu, AuditWorkUnit nestedFirst,
AuditWorkUnit nestedSecond) {
AuditWorkUnit nestedMerged = nestedSecond.dispatch(nestedFirst);
// Creating a new fake relation work unit with the nested merged data
return new FakeBidirectionalRelationWorkUnit(frwu, nestedMerged);
}
/**
* Describes a change to a single fake bidirectional relation.
*/
private static class FakeRelationChange {
private final Object owningEntity;
private final RelationDescription rd;
private final RevisionType revisionType;
private final Object index;
public FakeRelationChange(Object owningEntity, RelationDescription rd, RevisionType revisionType,
Object index) {
this.owningEntity = owningEntity;
this.rd = rd;
this.revisionType = revisionType;
this.index = index;
}
public RevisionType getRevisionType() {
return revisionType;
}
public void generateData(SessionImplementor sessionImplementor, Map<String, Object> data) {
// If the revision type is "DEL", it means that the object is removed from the collection. Then the
// new owner will in fact be null.
rd.getFakeBidirectionalRelationMapper().mapToMapFromEntity(sessionImplementor, data,
revisionType == RevisionType.DEL ? null : owningEntity, null);
// Also mapping the index, if the collection is indexed.
if (rd.getFakeBidirectionalRelationIndexMapper() != null) {
rd.getFakeBidirectionalRelationIndexMapper().mapToMapFromEntity(sessionImplementor, data,
revisionType == RevisionType.DEL ? null : index, null);
}
}
public static FakeRelationChange merge(FakeRelationChange first, FakeRelationChange second) {
if (first == null) { return second; }
if (second == null) { return first; }
/*
* The merging rules are the following (revision types of the first and second changes):
* - DEL, DEL - return any (the work units are the same)
* - DEL, ADD - return ADD (points to new owner)
* - ADD, DEL - return ADD (points to new owner)
* - ADD, ADD - return second (points to newer owner)
*/
if (first.getRevisionType() == RevisionType.DEL || second.getRevisionType() == RevisionType.ADD) {
return second;
} else {
return first;
}
}
}
}