HHH-4694:
- adding support for "fake" bidirectional many-to-one relations using an experimental @AuditMappedBy annotation - adding a new work unit, extracting common work unit code - upgrading TestNG to 5.10 git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18224 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
parent
fc54029a9f
commit
31f9f8ffc5
|
@ -95,7 +95,7 @@
|
|||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
<version>5.8</version>
|
||||
<version>5.10</version>
|
||||
<classifier>jdk15</classifier>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package org.hibernate.envers;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Annotation to specify a "fake" bi-directional relation. Such a relation uses {@code @OneToMany} +
|
||||
* {@code @JoinColumn} on the one side, and {@code @ManyToOne} + {@code @Column(insertable=false, updatable=false)} on
|
||||
* the many side. Then, Envers won't use a join table to audit this relation, but will store changes as in a normal
|
||||
* bi-directional relation.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This annotation is <b>experimental</b> and may change in future releases.
|
||||
* </p>
|
||||
*
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||
public @interface AuditMappedBy {
|
||||
/**
|
||||
* @return Name of the property in the related entity which maps back to this entity. The property should be
|
||||
* mapped with {@code @ManyToOne} and {@code @Column(insertable=false, updatable=false)}.
|
||||
*/
|
||||
String mappedBy();
|
||||
|
||||
/**
|
||||
* @return Name of the property in the related entity which maps to the position column. Should be specified only
|
||||
* for indexed collection, when @{@link org.hibernate.annotations.IndexColumn} is used on the collection.
|
||||
* The property should be mapped with {@code @Column(insertable=false, updatable=false)}.
|
||||
*/
|
||||
String positionMappedBy() default "";
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package org.hibernate.envers.configuration;
|
||||
|
||||
import org.hibernate.envers.configuration.metadata.reader.ClassAuditingData;
|
||||
import org.hibernate.mapping.PersistentClass;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* A helper class holding auditing meta-data for all persistent classes.
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
*/
|
||||
public class ClassesAuditingData {
|
||||
private final Map<String, ClassAuditingData> entityNameToAuditingData = new HashMap<String, ClassAuditingData>();
|
||||
private final Map<PersistentClass, ClassAuditingData> persistentClassToAuditingData = new LinkedHashMap<PersistentClass, ClassAuditingData>();
|
||||
|
||||
/**
|
||||
* Stores information about auditing meta-data for the given class.
|
||||
* @param pc Persistent class.
|
||||
* @param cad Auditing meta-data for the given class.
|
||||
*/
|
||||
public void addClassAuditingData(PersistentClass pc, ClassAuditingData cad) {
|
||||
entityNameToAuditingData.put(pc.getEntityName(), cad);
|
||||
persistentClassToAuditingData.put(pc, cad);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A collection of all auditing meta-data for persistent classes.
|
||||
*/
|
||||
public Collection<Map.Entry<PersistentClass, ClassAuditingData>> getAllClassAuditedData() {
|
||||
return persistentClassToAuditingData.entrySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entityName Name of the entity.
|
||||
* @return Auditing meta-data for the given entity.
|
||||
*/
|
||||
public ClassAuditingData getClassAuditingData(String entityName) {
|
||||
return entityNameToAuditingData.get(entityName);
|
||||
}
|
||||
}
|
|
@ -60,30 +60,36 @@ public class EntitiesConfigurator {
|
|||
Document revisionInfoXmlMapping, Element revisionInfoRelationMapping) {
|
||||
// Creating a name register to capture all audit entity names created.
|
||||
AuditEntityNameRegister auditEntityNameRegister = new AuditEntityNameRegister();
|
||||
|
||||
AuditMetadataGenerator auditMetaGen = new AuditMetadataGenerator(cfg, globalCfg, verEntCfg,
|
||||
revisionInfoRelationMapping, auditEntityNameRegister);
|
||||
DOMWriter writer = new DOMWriter();
|
||||
|
||||
// Sorting the persistent class topologically - superclass always before subclass
|
||||
Iterator<PersistentClass> classes = GraphTopologicalSort.sort(new PersistentClassGraphDefiner(cfg)).iterator();
|
||||
|
||||
Map<PersistentClass, ClassAuditingData> pcDatas =
|
||||
new HashMap<PersistentClass, ClassAuditingData>();
|
||||
ClassesAuditingData classesAuditingData = new ClassesAuditingData();
|
||||
Map<PersistentClass, EntityXmlMappingData> xmlMappings = new HashMap<PersistentClass, EntityXmlMappingData>();
|
||||
|
||||
// First pass
|
||||
// Reading metadata from annotations
|
||||
while (classes.hasNext()) {
|
||||
PersistentClass pc = classes.next();
|
||||
|
||||
// Collecting information from annotations on the persistent class pc
|
||||
AnnotationsMetadataReader annotationsMetadataReader =
|
||||
new AnnotationsMetadataReader(globalCfg, reflectionManager, pc);
|
||||
ClassAuditingData auditData = annotationsMetadataReader.getAuditData();
|
||||
|
||||
classesAuditingData.addClassAuditingData(pc, auditData);
|
||||
}
|
||||
|
||||
AuditMetadataGenerator auditMetaGen = new AuditMetadataGenerator(cfg, globalCfg, verEntCfg,
|
||||
revisionInfoRelationMapping, auditEntityNameRegister, classesAuditingData);
|
||||
|
||||
// First pass
|
||||
for (Map.Entry<PersistentClass, ClassAuditingData> pcDatasEntry : classesAuditingData.getAllClassAuditedData()) {
|
||||
PersistentClass pc = pcDatasEntry.getKey();
|
||||
ClassAuditingData auditData = pcDatasEntry.getValue();
|
||||
|
||||
EntityXmlMappingData xmlMappingData = new EntityXmlMappingData();
|
||||
if (auditData.isAudited()) {
|
||||
pcDatas.put(pc, auditData);
|
||||
|
||||
if (!StringTools.isEmpty(auditData.getAuditTable().value())) {
|
||||
verEntCfg.addCustomAuditTableName(pc.getEntityName(), auditData.getAuditTable().value());
|
||||
}
|
||||
|
@ -97,31 +103,29 @@ public class EntitiesConfigurator {
|
|||
}
|
||||
|
||||
// Second pass
|
||||
for (Map.Entry<PersistentClass, ClassAuditingData> pcDatasEntry : pcDatas.entrySet()) {
|
||||
for (Map.Entry<PersistentClass, ClassAuditingData> pcDatasEntry : classesAuditingData.getAllClassAuditedData()) {
|
||||
EntityXmlMappingData xmlMappingData = xmlMappings.get(pcDatasEntry.getKey());
|
||||
|
||||
auditMetaGen.generateSecondPass(pcDatasEntry.getKey(), pcDatasEntry.getValue(), xmlMappingData);
|
||||
if (pcDatasEntry.getValue().isAudited()) {
|
||||
auditMetaGen.generateSecondPass(pcDatasEntry.getKey(), pcDatasEntry.getValue(), xmlMappingData);
|
||||
try {
|
||||
cfg.addDocument(writer.write(xmlMappingData.getMainXmlMapping()));
|
||||
//writeDocument(xmlMappingData.getMainXmlMapping());
|
||||
|
||||
try {
|
||||
cfg.addDocument(writer.write(xmlMappingData.getMainXmlMapping()));
|
||||
// TODO
|
||||
//writeDocument(xmlMappingData.getMainXmlMapping());
|
||||
|
||||
for (Document additionalMapping : xmlMappingData.getAdditionalXmlMappings()) {
|
||||
cfg.addDocument(writer.write(additionalMapping));
|
||||
// TODO
|
||||
//writeDocument(additionalMapping);
|
||||
for (Document additionalMapping : xmlMappingData.getAdditionalXmlMappings()) {
|
||||
cfg.addDocument(writer.write(additionalMapping));
|
||||
//writeDocument(additionalMapping);
|
||||
}
|
||||
} catch (DocumentException e) {
|
||||
throw new MappingException(e);
|
||||
}
|
||||
} catch (DocumentException e) {
|
||||
throw new MappingException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Only if there are any versioned classes
|
||||
if (pcDatas.size() > 0) {
|
||||
if (classesAuditingData.getAllClassAuditedData().size() > 0) {
|
||||
try {
|
||||
if (revisionInfoXmlMapping != null) {
|
||||
// TODO
|
||||
//writeDocument(revisionInfoXmlMapping);
|
||||
cfg.addDocument(writer.write(revisionInfoXmlMapping));
|
||||
}
|
||||
|
@ -134,7 +138,7 @@ public class EntitiesConfigurator {
|
|||
auditMetaGen.getNotAuditedEntitiesConfigurations());
|
||||
}
|
||||
|
||||
// todo
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
private void writeDocument(Document e) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
Writer w = new PrintWriter(baos);
|
||||
|
|
|
@ -30,6 +30,7 @@ import java.util.Map;
|
|||
import org.dom4j.Element;
|
||||
import org.hibernate.envers.configuration.GlobalConfiguration;
|
||||
import org.hibernate.envers.configuration.AuditEntitiesConfiguration;
|
||||
import org.hibernate.envers.configuration.ClassesAuditingData;
|
||||
import org.hibernate.envers.configuration.metadata.reader.ClassAuditingData;
|
||||
import org.hibernate.envers.configuration.metadata.reader.PropertyAuditingData;
|
||||
import org.hibernate.envers.entities.EntityConfiguration;
|
||||
|
@ -62,15 +63,22 @@ public final class AuditMetadataGenerator {
|
|||
private final AuditEntitiesConfiguration verEntCfg;
|
||||
private final Element revisionInfoRelationMapping;
|
||||
|
||||
/*
|
||||
* Generators for different kinds of property values/types.
|
||||
*/
|
||||
private final BasicMetadataGenerator basicMetadataGenerator;
|
||||
private final ComponentMetadataGenerator componentMetadataGenerator;
|
||||
private final IdMetadataGenerator idMetadataGenerator;
|
||||
private final ToOneRelationMetadataGenerator toOneRelationMetadataGenerator;
|
||||
|
||||
/*
|
||||
* Here information about already generated mappings will be accumulated.
|
||||
*/
|
||||
private final Map<String, EntityConfiguration> entitiesConfigurations;
|
||||
private final Map<String, EntityConfiguration> notAuditedEntitiesConfigurations;
|
||||
|
||||
private final AuditEntityNameRegister auditEntityNameRegister;
|
||||
private final ClassesAuditingData classesAuditingData;
|
||||
|
||||
// Map entity name -> (join descriptor -> element describing the "versioned" join)
|
||||
private final Map<String, Map<Join, Element>> entitiesJoins;
|
||||
|
@ -78,7 +86,8 @@ public final class AuditMetadataGenerator {
|
|||
public AuditMetadataGenerator(Configuration cfg, GlobalConfiguration globalCfg,
|
||||
AuditEntitiesConfiguration verEntCfg,
|
||||
Element revisionInfoRelationMapping,
|
||||
AuditEntityNameRegister auditEntityNameRegister) {
|
||||
AuditEntityNameRegister auditEntityNameRegister,
|
||||
ClassesAuditingData classesAuditingData) {
|
||||
this.cfg = cfg;
|
||||
this.globalCfg = globalCfg;
|
||||
this.verEntCfg = verEntCfg;
|
||||
|
@ -90,6 +99,7 @@ public final class AuditMetadataGenerator {
|
|||
this.toOneRelationMetadataGenerator = new ToOneRelationMetadataGenerator(this);
|
||||
|
||||
this.auditEntityNameRegister = auditEntityNameRegister;
|
||||
this.classesAuditingData = classesAuditingData;
|
||||
|
||||
entitiesConfigurations = new HashMap<String, EntityConfiguration>();
|
||||
notAuditedEntitiesConfigurations = new HashMap<String, EntityConfiguration>();
|
||||
|
@ -448,6 +458,10 @@ public final class AuditMetadataGenerator {
|
|||
return entitiesConfigurations;
|
||||
}
|
||||
|
||||
public ClassesAuditingData getClassesAuditingData() {
|
||||
return classesAuditingData;
|
||||
}
|
||||
|
||||
// Getters for generators and configuration
|
||||
|
||||
BasicMetadataGenerator getBasicMetadataGenerator() {
|
||||
|
|
|
@ -40,13 +40,11 @@ import org.hibernate.envers.RelationTargetAuditMode;
|
|||
import org.hibernate.envers.configuration.metadata.reader.PropertyAuditingData;
|
||||
import org.hibernate.envers.entities.EntityConfiguration;
|
||||
import org.hibernate.envers.entities.IdMappingData;
|
||||
import org.hibernate.envers.entities.PropertyData;
|
||||
import org.hibernate.envers.entities.mapper.CompositeMapperBuilder;
|
||||
import org.hibernate.envers.entities.mapper.relation.BasicCollectionMapper;
|
||||
import org.hibernate.envers.entities.mapper.relation.CommonCollectionMapperData;
|
||||
import org.hibernate.envers.entities.mapper.relation.ListCollectionMapper;
|
||||
import org.hibernate.envers.entities.mapper.relation.MapCollectionMapper;
|
||||
import org.hibernate.envers.entities.mapper.relation.MiddleComponentData;
|
||||
import org.hibernate.envers.entities.mapper.relation.MiddleIdData;
|
||||
import org.hibernate.envers.entities.mapper.PropertyMapper;
|
||||
import org.hibernate.envers.entities.mapper.id.IdMapper;
|
||||
import org.hibernate.envers.entities.mapper.relation.*;
|
||||
import org.hibernate.envers.entities.mapper.relation.component.MiddleDummyComponentMapper;
|
||||
import org.hibernate.envers.entities.mapper.relation.component.MiddleMapKeyIdComponentMapper;
|
||||
import org.hibernate.envers.entities.mapper.relation.component.MiddleMapKeyPropertyComponentMapper;
|
||||
|
@ -61,6 +59,7 @@ import org.hibernate.envers.entities.mapper.relation.query.OneAuditEntityQueryGe
|
|||
import org.hibernate.envers.entities.mapper.relation.query.RelationQueryGenerator;
|
||||
import org.hibernate.envers.tools.StringTools;
|
||||
import org.hibernate.envers.tools.Tools;
|
||||
import org.hibernate.envers.tools.MappingTools;
|
||||
|
||||
import org.hibernate.MappingException;
|
||||
import org.hibernate.mapping.Collection;
|
||||
|
@ -79,12 +78,16 @@ import org.hibernate.type.SetType;
|
|||
import org.hibernate.type.SortedMapType;
|
||||
import org.hibernate.type.SortedSetType;
|
||||
import org.hibernate.type.Type;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Generates metadata for a collection-valued property.
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
*/
|
||||
public final class CollectionMetadataGenerator {
|
||||
private static final Logger log = LoggerFactory.getLogger(CollectionMetadataGenerator.class);
|
||||
|
||||
private final AuditMetadataGenerator mainGenerator;
|
||||
private final String propertyName;
|
||||
private final Collection propertyValue;
|
||||
|
@ -145,10 +148,13 @@ public final class CollectionMetadataGenerator {
|
|||
void addCollection() {
|
||||
Type type = propertyValue.getType();
|
||||
|
||||
if ((type instanceof BagType || type instanceof SetType || type instanceof MapType || type instanceof ListType) &&
|
||||
(propertyValue.getElement() instanceof OneToMany) && (propertyValue.isInverse())) {
|
||||
boolean oneToManyAttachedType = type instanceof BagType || type instanceof SetType || type instanceof MapType || type instanceof ListType;
|
||||
boolean inverseOneToMany = (propertyValue.getElement() instanceof OneToMany) && (propertyValue.isInverse());
|
||||
boolean fakeOneToManyBidirectional = (propertyValue.getElement() instanceof OneToMany) && (propertyAuditingData.getAuditMappedBy() != null);
|
||||
|
||||
if (oneToManyAttachedType && (inverseOneToMany || fakeOneToManyBidirectional)) {
|
||||
// A one-to-many relation mapped using @ManyToOne and @OneToMany(mappedBy="...")
|
||||
addOneToManyAttached();
|
||||
addOneToManyAttached(fakeOneToManyBidirectional);
|
||||
} else {
|
||||
// All other kinds of relations require a middle (join) table.
|
||||
addWithMiddleTable();
|
||||
|
@ -161,7 +167,10 @@ public final class CollectionMetadataGenerator {
|
|||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private void addOneToManyAttached() {
|
||||
private void addOneToManyAttached(boolean fakeOneToManyBidirectional) {
|
||||
log.debug("Adding audit mapping for property " + referencingEntityName + "." + propertyName +
|
||||
": one-to-many collection, using a join column on the referenced entity.");
|
||||
|
||||
String mappedBy = getMappedBy(propertyValue);
|
||||
|
||||
IdMappingData referencedIdMapping = mainGenerator.getReferencedIdMappingData(referencingEntityName,
|
||||
|
@ -200,9 +209,29 @@ public final class CollectionMetadataGenerator {
|
|||
// Checking the type of the collection and adding an appropriate mapper.
|
||||
addMapper(commonCollectionMapperData, elementComponentData, indexComponentData);
|
||||
|
||||
PropertyMapper fakeBidirectionalRelationMapper;
|
||||
if (fakeOneToManyBidirectional) {
|
||||
// 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).
|
||||
String auditMappedBy = propertyAuditingData.getAuditMappedBy();
|
||||
|
||||
// Creating a prefixed relation mapper.
|
||||
IdMapper relMapper = referencingIdMapping.getIdMapper().prefixMappedProperties(
|
||||
MappingTools.createToOneRelationPrefix(auditMappedBy));
|
||||
|
||||
fakeBidirectionalRelationMapper = new ToOneIdMapper(
|
||||
relMapper,
|
||||
// The mapper will only be used to map from entity to map, so no need to provide other details
|
||||
// when constructing the PropertyData.
|
||||
new PropertyData(auditMappedBy, null, null, null),
|
||||
referencedEntityName);
|
||||
} else {
|
||||
fakeBidirectionalRelationMapper = null;
|
||||
}
|
||||
|
||||
// Storing information about this relation.
|
||||
referencingEntityConfiguration.addToManyNotOwningRelation(propertyName, mappedBy,
|
||||
referencedEntityName, referencingIdData.getPrefixedMapper());
|
||||
referencedEntityName, referencingIdData.getPrefixedMapper(), fakeBidirectionalRelationMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -237,6 +266,9 @@ public final class CollectionMetadataGenerator {
|
|||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private void addWithMiddleTable() {
|
||||
log.debug("Adding audit mapping for property " + referencingEntityName + "." + propertyName +
|
||||
": collection with a join table.");
|
||||
|
||||
// Generating the name of the middle table
|
||||
String auditMiddleTableName;
|
||||
String auditMiddleEntityName;
|
||||
|
@ -249,6 +281,8 @@ public final class CollectionMetadataGenerator {
|
|||
auditMiddleEntityName = mainGenerator.getVerEntCfg().getAuditEntityName(middleTableName);
|
||||
}
|
||||
|
||||
log.debug("Using join table name: " + auditMiddleTableName);
|
||||
|
||||
// Generating the XML mapping for the middle entity, only if the relation isn't inverse.
|
||||
// If the relation is inverse, will be later checked by comparing middleEntityXml with null.
|
||||
Element middleEntityXml;
|
||||
|
@ -413,8 +447,8 @@ public final class CollectionMetadataGenerator {
|
|||
} else {
|
||||
// Last but one parameter: collection components are always insertable
|
||||
boolean mapped = mainGenerator.getBasicMetadataGenerator().addBasic(xmlMapping,
|
||||
new PropertyAuditingData(prefix, "field", ModificationStore.FULL, RelationTargetAuditMode.AUDITED), value, null,
|
||||
true, true);
|
||||
new PropertyAuditingData(prefix, "field", ModificationStore.FULL, RelationTargetAuditMode.AUDITED, null, null),
|
||||
value, null, true, true);
|
||||
|
||||
if (mapped) {
|
||||
// Simple values are always stored in the first item of the array returned by the query generator.
|
||||
|
@ -499,10 +533,34 @@ public final class CollectionMetadataGenerator {
|
|||
return middleEntityXmlId;
|
||||
}
|
||||
|
||||
private String getMappedByCommon(PersistentClass referencedClass) {
|
||||
// If there's an @AuditMappedBy specified, returning it directly.
|
||||
String auditMappedBy = propertyAuditingData.getAuditMappedBy();
|
||||
if (auditMappedBy != null) {
|
||||
// Checking that the property exists.
|
||||
try {
|
||||
referencedClass.getProperty(auditMappedBy);
|
||||
} catch (MappingException me) {
|
||||
throw new MappingException("@AuditMappedBy points to a property that can be read: " +
|
||||
referencedClass.getEntityName() + "." + auditMappedBy, me);
|
||||
}
|
||||
|
||||
return auditMappedBy;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private String getMappedBy(Collection collectionValue) {
|
||||
Iterator<Property> assocClassProps =
|
||||
((OneToMany) collectionValue.getElement()).getAssociatedClass().getPropertyIterator();
|
||||
PersistentClass referencedClass = ((OneToMany) collectionValue.getElement()).getAssociatedClass();
|
||||
|
||||
String mappedByCommon = getMappedByCommon(referencedClass);
|
||||
if (mappedByCommon != null) {
|
||||
return mappedByCommon;
|
||||
}
|
||||
|
||||
Iterator<Property> assocClassProps = referencedClass.getPropertyIterator();
|
||||
|
||||
while (assocClassProps.hasNext()) {
|
||||
Property property = assocClassProps.next();
|
||||
|
@ -519,6 +577,11 @@ public final class CollectionMetadataGenerator {
|
|||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private String getMappedBy(Table collectionTable, PersistentClass referencedClass) {
|
||||
String mappedByCommon = getMappedByCommon(referencedClass);
|
||||
if (mappedByCommon != null) {
|
||||
return mappedByCommon;
|
||||
}
|
||||
|
||||
Iterator<Property> properties = referencedClass.getPropertyIterator();
|
||||
while (properties.hasNext()) {
|
||||
Property property = properties.next();
|
||||
|
|
|
@ -139,6 +139,6 @@ public final class IdMetadataGenerator {
|
|||
|
||||
private PropertyAuditingData getIdPersistentPropertyAuditingData(Property property) {
|
||||
return new PropertyAuditingData(property.getName(), property.getPropertyAccessorName(),
|
||||
ModificationStore.FULL, RelationTargetAuditMode.AUDITED);
|
||||
ModificationStore.FULL, RelationTargetAuditMode.AUDITED, null, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,17 +32,23 @@ import org.hibernate.envers.entities.mapper.id.IdMapper;
|
|||
import org.hibernate.envers.entities.mapper.relation.OneToOneNotOwningMapper;
|
||||
import org.hibernate.envers.entities.mapper.relation.ToOneIdMapper;
|
||||
import org.hibernate.envers.configuration.metadata.reader.PropertyAuditingData;
|
||||
import org.hibernate.envers.configuration.metadata.reader.ClassAuditingData;
|
||||
import org.hibernate.envers.tools.MappingTools;
|
||||
|
||||
import org.hibernate.MappingException;
|
||||
import org.hibernate.mapping.OneToOne;
|
||||
import org.hibernate.mapping.ToOne;
|
||||
import org.hibernate.mapping.Value;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Generates metadata for to-one relations (reference-valued properties).
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
*/
|
||||
public final class ToOneRelationMetadataGenerator {
|
||||
private static final Logger log = LoggerFactory.getLogger(ToOneRelationMetadataGenerator.class);
|
||||
|
||||
private final AuditMetadataGenerator mainGenerator;
|
||||
|
||||
ToOneRelationMetadataGenerator(AuditMetadataGenerator auditMetadataGenerator) {
|
||||
|
@ -57,7 +63,7 @@ public final class ToOneRelationMetadataGenerator {
|
|||
IdMappingData idMapping = mainGenerator.getReferencedIdMappingData(entityName, referencedEntityName,
|
||||
propertyAuditingData, true);
|
||||
|
||||
String lastPropertyPrefix = propertyAuditingData.getName() + "_";
|
||||
String lastPropertyPrefix = MappingTools.createToOneRelationPrefix(propertyAuditingData.getName());
|
||||
|
||||
// Generating the id mapper for the relation
|
||||
IdMapper relMapper = idMapping.getIdMapper().prefixMappedProperties(lastPropertyPrefix);
|
||||
|
@ -66,6 +72,30 @@ public final class ToOneRelationMetadataGenerator {
|
|||
mainGenerator.getEntitiesConfigurations().get(entityName).addToOneRelation(
|
||||
propertyAuditingData.getName(), referencedEntityName, relMapper);
|
||||
|
||||
// If the property isn't insertable, checking if this is not a "fake" bidirectional many-to-one relationship,
|
||||
// that is, when the one side owns the relation (and is a collection), and the many side is non insertable.
|
||||
// When that's the case and the user specified to store this relation without a middle table (using
|
||||
// @AuditMappedBy), we have to make the property insertable for the purposes of Envers. In case of changes to
|
||||
// the entity that didn't involve the relation, it's value will then be stored properly. In case of changes
|
||||
// to the entity that did involve the relation, it's the responsibility of the collection side to store the
|
||||
// proper data.
|
||||
if (!insertable) {
|
||||
ClassAuditingData referencedAuditingData = mainGenerator.getClassesAuditingData().getClassAuditingData(referencedEntityName);
|
||||
|
||||
// Looking through the properties of the referenced entity to find the right property.
|
||||
for (String referencedPropertyName : referencedAuditingData.getPropertyNames()) {
|
||||
String auditMappedBy = referencedAuditingData.getPropertyAuditingData(referencedPropertyName).getAuditMappedBy();
|
||||
if (propertyAuditingData.getName().equals(auditMappedBy)) {
|
||||
log.debug("Non-insertable property " + entityName + "." + propertyAuditingData.getName() +
|
||||
" will be made insertable because a matching @AuditMappedBy was found in the " +
|
||||
referencedEntityName + " entity.");
|
||||
|
||||
insertable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adding an element to the mapping corresponding to the references entity id's
|
||||
Element properties = (Element) idMapping.getXmlRelationMapping().clone();
|
||||
properties.addAttribute("name", propertyAuditingData.getName());
|
||||
|
@ -97,7 +127,7 @@ public final class ToOneRelationMetadataGenerator {
|
|||
throw new MappingException("An audited relation to a non-audited entity " + entityName + "!");
|
||||
}
|
||||
|
||||
String lastPropertyPrefix = owningReferencePropertyName + "_";
|
||||
String lastPropertyPrefix = MappingTools.createToOneRelationPrefix(owningReferencePropertyName);
|
||||
String referencedEntityName = propertyValue.getReferencedEntityName();
|
||||
|
||||
// Generating the id mapper for the relation
|
||||
|
|
|
@ -14,12 +14,7 @@ import javax.persistence.Version;
|
|||
import org.hibernate.annotations.common.reflection.XClass;
|
||||
import org.hibernate.annotations.common.reflection.XProperty;
|
||||
import org.hibernate.annotations.common.reflection.ReflectionManager;
|
||||
import org.hibernate.envers.AuditJoinTable;
|
||||
import org.hibernate.envers.AuditOverride;
|
||||
import org.hibernate.envers.AuditOverrides;
|
||||
import org.hibernate.envers.Audited;
|
||||
import org.hibernate.envers.ModificationStore;
|
||||
import org.hibernate.envers.NotAudited;
|
||||
import org.hibernate.envers.*;
|
||||
import org.hibernate.envers.configuration.GlobalConfiguration;
|
||||
import org.hibernate.envers.tools.MappingTools;
|
||||
import org.hibernate.mapping.Component;
|
||||
|
@ -177,10 +172,21 @@ public class AuditedPropertiesReader {
|
|||
return false; // not audited due to AuditOverride annotation
|
||||
}
|
||||
addPropertyMapKey(property, propertyData);
|
||||
setPropertyAuditMappedBy(property, propertyData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setPropertyAuditMappedBy(XProperty property, PropertyAuditingData propertyData) {
|
||||
AuditMappedBy auditMappedBy = property.getAnnotation(AuditMappedBy.class);
|
||||
if (auditMappedBy != null) {
|
||||
propertyData.setAuditMappedBy(auditMappedBy.mappedBy());
|
||||
if (!"".equals(auditMappedBy.positionMappedBy())) {
|
||||
propertyData.setPositionMappedBy(auditMappedBy.positionMappedBy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addPropertyMapKey(XProperty property, PropertyAuditingData propertyData) {
|
||||
MapKey mapKey = property.getAnnotation(MapKey.class);
|
||||
if (mapKey != null) {
|
||||
|
@ -254,7 +260,7 @@ public class AuditedPropertiesReader {
|
|||
public Class<? extends Annotation> annotationType() { return this.getClass(); }
|
||||
};
|
||||
|
||||
private class ComponentPropertiesSource implements PersistentPropertiesSource {
|
||||
private class ComponentPropertiesSource implements PersistentPropertiesSource {
|
||||
private final XClass xclass;
|
||||
private final Component component;
|
||||
|
||||
|
|
|
@ -57,6 +57,10 @@ public class ClassAuditingData implements AuditedPropertiesHolder {
|
|||
return properties.get(propertyName);
|
||||
}
|
||||
|
||||
public Iterable<String> getPropertyNames() {
|
||||
return properties.keySet();
|
||||
}
|
||||
|
||||
public Map<String, String> getSecondaryTableDictionary() {
|
||||
return secondaryTableDictionary;
|
||||
}
|
||||
|
|
|
@ -46,17 +46,22 @@ public class PropertyAuditingData {
|
|||
private String accessType;
|
||||
private final List<AuditOverride> auditJoinTableOverrides = new ArrayList<AuditOverride>(0);
|
||||
private RelationTargetAuditMode relationTargetAuditMode;
|
||||
private String auditMappedBy;
|
||||
private String positionMappedBy;
|
||||
|
||||
public PropertyAuditingData() {
|
||||
}
|
||||
|
||||
public PropertyAuditingData(String name, String accessType, ModificationStore store,
|
||||
RelationTargetAuditMode relationTargetAuditMode) {
|
||||
RelationTargetAuditMode relationTargetAuditMode,
|
||||
String auditMappedBy, String positionMappedBy) {
|
||||
this.name = name;
|
||||
this.beanName = name;
|
||||
this.accessType = accessType;
|
||||
this.store = store;
|
||||
this.relationTargetAuditMode = relationTargetAuditMode;
|
||||
this.auditMappedBy = auditMappedBy;
|
||||
this.positionMappedBy = positionMappedBy;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
|
@ -115,7 +120,23 @@ public class PropertyAuditingData {
|
|||
return auditJoinTableOverrides;
|
||||
}
|
||||
|
||||
public void addAuditingOverride(AuditOverride annotation) {
|
||||
public String getAuditMappedBy() {
|
||||
return auditMappedBy;
|
||||
}
|
||||
|
||||
public void setAuditMappedBy(String auditMappedBy) {
|
||||
this.auditMappedBy = auditMappedBy;
|
||||
}
|
||||
|
||||
public String getPositionMappedBy() {
|
||||
return positionMappedBy;
|
||||
}
|
||||
|
||||
public void setPositionMappedBy(String positionMappedBy) {
|
||||
this.positionMappedBy = positionMappedBy;
|
||||
}
|
||||
|
||||
public void addAuditingOverride(AuditOverride annotation) {
|
||||
if (annotation != null) {
|
||||
String overrideName = annotation.name();
|
||||
boolean present = false;
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
|
||||
import org.hibernate.envers.entities.mapper.ExtendedPropertyMapper;
|
||||
import org.hibernate.envers.entities.mapper.PropertyMapper;
|
||||
import org.hibernate.envers.entities.mapper.id.IdMapper;
|
||||
|
||||
/**
|
||||
|
@ -52,29 +53,29 @@ public class EntityConfiguration {
|
|||
|
||||
public void addToOneRelation(String fromPropertyName, String toEntityName, IdMapper idMapper) {
|
||||
relations.put(fromPropertyName, new RelationDescription(fromPropertyName, RelationType.TO_ONE,
|
||||
toEntityName, null, idMapper));
|
||||
toEntityName, null, idMapper, null));
|
||||
}
|
||||
|
||||
public void addToOneNotOwningRelation(String fromPropertyName, String mappedByPropertyName, String toEntityName,
|
||||
IdMapper idMapper) {
|
||||
relations.put(fromPropertyName, new RelationDescription(fromPropertyName, RelationType.TO_ONE_NOT_OWNING,
|
||||
toEntityName, mappedByPropertyName, idMapper));
|
||||
toEntityName, mappedByPropertyName, idMapper, null));
|
||||
}
|
||||
|
||||
public void addToManyNotOwningRelation(String fromPropertyName, String mappedByPropertyName, String toEntityName,
|
||||
IdMapper idMapper) {
|
||||
IdMapper idMapper, PropertyMapper fakeBidirectionalRelationMapper) {
|
||||
relations.put(fromPropertyName, new RelationDescription(fromPropertyName, RelationType.TO_MANY_NOT_OWNING,
|
||||
toEntityName, mappedByPropertyName, idMapper));
|
||||
toEntityName, mappedByPropertyName, idMapper, fakeBidirectionalRelationMapper));
|
||||
}
|
||||
|
||||
public void addToManyMiddleRelation(String fromPropertyName, String toEntityName) {
|
||||
relations.put(fromPropertyName, new RelationDescription(fromPropertyName, RelationType.TO_MANY_MIDDLE,
|
||||
toEntityName, null, null));
|
||||
toEntityName, null, null, null));
|
||||
}
|
||||
|
||||
public void addToManyMiddleNotOwningRelation(String fromPropertyName, String mappedByPropertyName, String toEntityName) {
|
||||
relations.put(fromPropertyName, new RelationDescription(fromPropertyName, RelationType.TO_MANY_MIDDLE_NOT_OWNING,
|
||||
toEntityName, mappedByPropertyName, null));
|
||||
toEntityName, mappedByPropertyName, null, null));
|
||||
}
|
||||
|
||||
public boolean isRelation(String propertyName) {
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
package org.hibernate.envers.entities;
|
||||
|
||||
import org.hibernate.envers.entities.mapper.id.IdMapper;
|
||||
import org.hibernate.envers.entities.mapper.PropertyMapper;
|
||||
|
||||
/**
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
|
@ -34,15 +35,18 @@ public class RelationDescription {
|
|||
private final String toEntityName;
|
||||
private final String mappedByPropertyName;
|
||||
private final IdMapper idMapper;
|
||||
private final PropertyMapper fakeBidirectionalRelationMapper;
|
||||
private boolean bidirectional;
|
||||
|
||||
public RelationDescription(String fromPropertyName, RelationType relationType, String toEntityName,
|
||||
String mappedByPropertyName, IdMapper idMapper) {
|
||||
String mappedByPropertyName, IdMapper idMapper,
|
||||
PropertyMapper fakeBidirectionalRelationMapper) {
|
||||
this.fromPropertyName = fromPropertyName;
|
||||
this.relationType = relationType;
|
||||
this.toEntityName = toEntityName;
|
||||
this.mappedByPropertyName = mappedByPropertyName;
|
||||
this.idMapper = idMapper;
|
||||
this.fakeBidirectionalRelationMapper = fakeBidirectionalRelationMapper;
|
||||
|
||||
this.bidirectional = false;
|
||||
}
|
||||
|
@ -67,6 +71,10 @@ public class RelationDescription {
|
|||
return idMapper;
|
||||
}
|
||||
|
||||
public PropertyMapper getFakeBidirectionalRelationMapper() {
|
||||
return fakeBidirectionalRelationMapper;
|
||||
}
|
||||
|
||||
public boolean isBidirectional() {
|
||||
return bidirectional;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
package org.hibernate.envers.event;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.envers.configuration.AuditConfiguration;
|
||||
import org.hibernate.envers.entities.RelationDescription;
|
||||
|
@ -31,12 +32,9 @@ import org.hibernate.envers.entities.RelationType;
|
|||
import org.hibernate.envers.entities.mapper.PersistentCollectionChangeData;
|
||||
import org.hibernate.envers.entities.mapper.id.IdMapper;
|
||||
import org.hibernate.envers.synchronization.AuditSync;
|
||||
import org.hibernate.envers.synchronization.work.AddWorkUnit;
|
||||
import org.hibernate.envers.synchronization.work.CollectionChangeWorkUnit;
|
||||
import org.hibernate.envers.synchronization.work.DelWorkUnit;
|
||||
import org.hibernate.envers.synchronization.work.ModWorkUnit;
|
||||
import org.hibernate.envers.synchronization.work.PersistentCollectionChangeWorkUnit;
|
||||
import org.hibernate.envers.synchronization.work.*;
|
||||
import org.hibernate.envers.tools.Tools;
|
||||
import org.hibernate.envers.RevisionType;
|
||||
|
||||
import org.hibernate.cfg.Configuration;
|
||||
import org.hibernate.collection.PersistentCollection;
|
||||
|
@ -57,6 +55,7 @@ import org.hibernate.event.PreCollectionRemoveEventListener;
|
|||
import org.hibernate.event.PreCollectionUpdateEvent;
|
||||
import org.hibernate.event.PreCollectionUpdateEventListener;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.persister.collection.AbstractCollectionPersister;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
|
||||
/**
|
||||
|
@ -182,7 +181,8 @@ public class AuditEventListener implements PostInsertEventListener, PostUpdateEv
|
|||
}
|
||||
|
||||
private void generateBidirectionalCollectionChangeWorkUnits(AuditSync verSync, AbstractCollectionEvent event,
|
||||
PersistentCollectionChangeWorkUnit workUnit) {
|
||||
PersistentCollectionChangeWorkUnit workUnit,
|
||||
RelationDescription rd) {
|
||||
// Checking if this is enabled in configuration ...
|
||||
if (!verCfg.getGlobalCfg().isGenerateRevisionsForCollections()) {
|
||||
return;
|
||||
|
@ -190,12 +190,9 @@ public class AuditEventListener implements PostInsertEventListener, PostUpdateEv
|
|||
|
||||
// Checking if this is not a bidirectional relation - then, a revision needs also be generated for
|
||||
// the other side of the relation.
|
||||
RelationDescription relDesc = verCfg.getEntCfg().getRelationDescription(event.getAffectedOwnerEntityName(),
|
||||
workUnit.getReferencingPropertyName());
|
||||
|
||||
// relDesc can be null if this is a collection of simple values (not a relation).
|
||||
if (relDesc != null && relDesc.isBidirectional()) {
|
||||
String relatedEntityName = relDesc.getToEntityName();
|
||||
if (rd != null && rd.isBidirectional()) {
|
||||
String relatedEntityName = rd.getToEntityName();
|
||||
IdMapper relatedIdMapper = verCfg.getEntCfg().get(relatedEntityName).getIdMapper();
|
||||
|
||||
for (PersistentCollectionChangeData changeData : workUnit.getCollectionChanges()) {
|
||||
|
@ -208,6 +205,38 @@ public class AuditEventListener implements PostInsertEventListener, PostUpdateEv
|
|||
}
|
||||
}
|
||||
|
||||
private void generateFakeBidirecationalRelationWorkUnits(AuditSync verSync, PersistentCollection newColl, Serializable oldColl,
|
||||
String collectionEntityName, String referencingPropertyName,
|
||||
AbstractCollectionEvent event,
|
||||
RelationDescription rd) {
|
||||
// First computing the relation changes
|
||||
List<PersistentCollectionChangeData> collectionChanges = verCfg.getEntCfg().get(collectionEntityName).getPropertyMapper()
|
||||
.mapCollectionChanges(referencingPropertyName, newColl, oldColl, event.getAffectedOwnerIdOrNull());
|
||||
|
||||
// Getting the id mapper for the related entity, as the work units generated will corrspond to the related
|
||||
// entities.
|
||||
String relatedEntityName = rd.getToEntityName();
|
||||
IdMapper relatedIdMapper = verCfg.getEntCfg().get(relatedEntityName).getIdMapper();
|
||||
|
||||
// For each collection change, generating the bidirectional work unit.
|
||||
for (PersistentCollectionChangeData changeData : collectionChanges) {
|
||||
Object relatedObj = changeData.getChangedElement();
|
||||
Serializable relatedId = (Serializable) relatedIdMapper.mapToIdFromEntity(relatedObj);
|
||||
RevisionType revType = (RevisionType) changeData.getData().get(verCfg.getAuditEntCfg().getRevisionTypePropName());
|
||||
|
||||
// By default, the nested work unit is a collection change work unit.
|
||||
AuditWorkUnit nestedWorkUnit = new CollectionChangeWorkUnit(event.getSession(), relatedEntityName, verCfg,
|
||||
relatedId, relatedObj);
|
||||
|
||||
verSync.addWorkUnit(new FakeBidirectionalRelationWorkUnit(event.getSession(), relatedEntityName, verCfg,
|
||||
relatedId, event.getAffectedOwnerOrNull(), rd, revType, nestedWorkUnit));
|
||||
}
|
||||
|
||||
// We also have to generate a collection change work unit for the owning entity.
|
||||
verSync.addWorkUnit(new CollectionChangeWorkUnit(event.getSession(), collectionEntityName, verCfg,
|
||||
event.getAffectedOwnerIdOrNull(), event.getAffectedOwnerOrNull()));
|
||||
}
|
||||
|
||||
private void onCollectionAction(AbstractCollectionEvent event, PersistentCollection newColl, Serializable oldColl,
|
||||
CollectionEntry collectionEntry) {
|
||||
String entityName = event.getAffectedOwnerEntityName();
|
||||
|
@ -215,16 +244,28 @@ public class AuditEventListener implements PostInsertEventListener, PostUpdateEv
|
|||
if (verCfg.getEntCfg().isVersioned(entityName)) {
|
||||
AuditSync verSync = verCfg.getSyncManager().get(event.getSession());
|
||||
|
||||
PersistentCollectionChangeWorkUnit workUnit = new PersistentCollectionChangeWorkUnit(event.getSession(),
|
||||
entityName, verCfg, newColl, collectionEntry, oldColl, event.getAffectedOwnerIdOrNull());
|
||||
verSync.addWorkUnit(workUnit);
|
||||
String ownerEntityName = ((AbstractCollectionPersister) collectionEntry.getLoadedPersister()).getOwnerEntityName();
|
||||
String referencingPropertyName = collectionEntry.getRole().substring(ownerEntityName.length() + 1);
|
||||
|
||||
if (workUnit.containsWork()) {
|
||||
// There are some changes: a revision needs also be generated for the collection owner
|
||||
verSync.addWorkUnit(new CollectionChangeWorkUnit(event.getSession(), event.getAffectedOwnerEntityName(),
|
||||
verCfg, event.getAffectedOwnerIdOrNull(), event.getAffectedOwnerOrNull()));
|
||||
// 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.
|
||||
RelationDescription rd = verCfg.getEntCfg().get(entityName).getRelationDescription(referencingPropertyName);
|
||||
if (rd != null && rd.getMappedByPropertyName() != null) {
|
||||
generateFakeBidirecationalRelationWorkUnits(verSync, newColl, oldColl, entityName,
|
||||
referencingPropertyName, event, rd);
|
||||
} else {
|
||||
PersistentCollectionChangeWorkUnit workUnit = new PersistentCollectionChangeWorkUnit(event.getSession(),
|
||||
entityName, verCfg, newColl, collectionEntry, oldColl, event.getAffectedOwnerIdOrNull(),
|
||||
referencingPropertyName);
|
||||
verSync.addWorkUnit(workUnit);
|
||||
|
||||
generateBidirectionalCollectionChangeWorkUnits(verSync, event, workUnit);
|
||||
if (workUnit.containsWork()) {
|
||||
// There are some changes: a revision needs also be generated for the collection owner
|
||||
verSync.addWorkUnit(new CollectionChangeWorkUnit(event.getSession(), event.getAffectedOwnerEntityName(),
|
||||
verCfg, event.getAffectedOwnerIdOrNull(), event.getAffectedOwnerOrNull()));
|
||||
|
||||
generateBidirectionalCollectionChangeWorkUnits(verSync, event, workUnit, rd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,6 +64,14 @@ public abstract class AbstractAuditWorkUnit implements AuditWorkUnit {
|
|||
data.put(entitiesCfg.getOriginalIdPropName(), originalId);
|
||||
}
|
||||
|
||||
public void perform(Session session, Object revisionData) {
|
||||
Map<String, Object> data = generateData(revisionData);
|
||||
|
||||
session.save(verCfg.getAuditEntCfg().getAuditEntityName(getEntityName()), data);
|
||||
|
||||
setPerformed(data);
|
||||
}
|
||||
|
||||
public Object getEntityId() {
|
||||
return id;
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ import java.util.Map;
|
|||
import org.hibernate.envers.RevisionType;
|
||||
import org.hibernate.envers.configuration.AuditConfiguration;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.engine.SessionImplementor;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
||||
|
@ -60,12 +59,9 @@ public class AddWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
|
|||
return true;
|
||||
}
|
||||
|
||||
public void perform(Session session, Object revisionData) {
|
||||
public Map<String, Object> generateData(Object revisionData) {
|
||||
fillDataWithId(data, revisionData, RevisionType.ADD);
|
||||
|
||||
session.save(verCfg.getAuditEntCfg().getAuditEntityName(getEntityName()), data);
|
||||
|
||||
setPerformed(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public AuditWorkUnit merge(AddWorkUnit second) {
|
||||
|
@ -84,6 +80,10 @@ public class AddWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
|
|||
return this;
|
||||
}
|
||||
|
||||
public AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second) {
|
||||
return FakeBidirectionalRelationWorkUnit.merge(second, this, second.getNestedWorkUnit());
|
||||
}
|
||||
|
||||
public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
|
||||
return first.merge(this);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,10 @@ package org.hibernate.envers.synchronization.work;
|
|||
|
||||
import org.hibernate.Session;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* TODO: refactor constructors into factory methods
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
*/
|
||||
public interface AuditWorkUnit extends WorkUnitMergeVisitor, WorkUnitMergeDispatcher {
|
||||
|
@ -36,6 +39,19 @@ public interface AuditWorkUnit extends WorkUnitMergeVisitor, WorkUnitMergeDispat
|
|||
|
||||
boolean isPerformed();
|
||||
|
||||
/**
|
||||
* Perform this work unit in the given session.
|
||||
* @param session Session, in which the work unit should be performed.
|
||||
* @param revisionData The current revision data, which will be used to populate the work unit with the correct
|
||||
* revision relation.
|
||||
*/
|
||||
void perform(Session session, Object revisionData);
|
||||
void undo(Session session);
|
||||
|
||||
/**
|
||||
* @param revisionData The current revision data, which will be used to populate the work unit with the correct
|
||||
* revision relation.
|
||||
* @return Generates data that should be saved when performing this work unit.
|
||||
*/
|
||||
Map<String, Object> generateData(Object revisionData);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ import java.util.Map;
|
|||
import org.hibernate.envers.RevisionType;
|
||||
import org.hibernate.envers.configuration.AuditConfiguration;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.engine.SessionImplementor;
|
||||
|
||||
/**
|
||||
|
@ -50,16 +49,14 @@ public class CollectionChangeWorkUnit extends AbstractAuditWorkUnit implements A
|
|||
return true;
|
||||
}
|
||||
|
||||
public void perform(Session session, Object revisionData) {
|
||||
public Map<String, Object> generateData(Object revisionData) {
|
||||
Map<String, Object> data = new HashMap<String, Object>();
|
||||
fillDataWithId(data, revisionData, RevisionType.MOD);
|
||||
|
||||
verCfg.getEntCfg().get(getEntityName()).getPropertyMapper().mapToMapFromEntity(sessionImplementor,
|
||||
data, entity, null);
|
||||
|
||||
session.save(verCfg.getAuditEntCfg().getAuditEntityName(getEntityName()), data);
|
||||
|
||||
setPerformed(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public AuditWorkUnit merge(AddWorkUnit second) {
|
||||
|
@ -78,6 +75,10 @@ public class CollectionChangeWorkUnit extends AbstractAuditWorkUnit implements A
|
|||
return this;
|
||||
}
|
||||
|
||||
public AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second) {
|
||||
return second;
|
||||
}
|
||||
|
||||
public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
|
||||
return first.merge(this);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ import java.util.Map;
|
|||
import org.hibernate.envers.RevisionType;
|
||||
import org.hibernate.envers.configuration.AuditConfiguration;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.engine.SessionImplementor;
|
||||
|
||||
|
@ -53,7 +52,7 @@ public class DelWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
|
|||
return true;
|
||||
}
|
||||
|
||||
public void perform(Session session, Object revisionData) {
|
||||
public Map<String, Object> generateData(Object revisionData) {
|
||||
Map<String, Object> data = new HashMap<String, Object>();
|
||||
fillDataWithId(data, revisionData, RevisionType.DEL);
|
||||
|
||||
|
@ -62,9 +61,7 @@ public class DelWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
|
|||
propertyNames, state, state);
|
||||
}
|
||||
|
||||
session.save(verCfg.getAuditEntCfg().getAuditEntityName(getEntityName()), data);
|
||||
|
||||
setPerformed(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public AuditWorkUnit merge(AddWorkUnit second) {
|
||||
|
@ -83,6 +80,10 @@ public class DelWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
|
|||
return this;
|
||||
}
|
||||
|
||||
public AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
|
||||
return first.merge(this);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
package org.hibernate.envers.synchronization.work;
|
||||
|
||||
import org.hibernate.engine.SessionImplementor;
|
||||
import org.hibernate.envers.configuration.AuditConfiguration;
|
||||
import org.hibernate.envers.entities.RelationDescription;
|
||||
import org.hibernate.envers.RevisionType;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* 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 Object owningEntity;
|
||||
private final RelationDescription rd;
|
||||
private final RevisionType revisionType;
|
||||
|
||||
/*
|
||||
* 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, Object owningEntity,
|
||||
RelationDescription rd, RevisionType revisionType,
|
||||
AuditWorkUnit nestedWorkUnit) {
|
||||
super(sessionImplementor, entityName, verCfg, id);
|
||||
|
||||
|
||||
this.owningEntity = owningEntity;
|
||||
this.rd = rd;
|
||||
this.revisionType = revisionType;
|
||||
this.nestedWorkUnit = nestedWorkUnit;
|
||||
}
|
||||
|
||||
public FakeBidirectionalRelationWorkUnit(FakeBidirectionalRelationWorkUnit original, AuditWorkUnit nestedWorkUnit) {
|
||||
super(original.sessionImplementor, original.entityName, original.verCfg, original.id);
|
||||
|
||||
this.owningEntity = original.owningEntity;
|
||||
this.rd = original.rd;
|
||||
this.revisionType = original.revisionType;
|
||||
this.nestedWorkUnit = nestedWorkUnit;
|
||||
}
|
||||
|
||||
public AuditWorkUnit getNestedWorkUnit() {
|
||||
return nestedWorkUnit;
|
||||
}
|
||||
|
||||
public RevisionType getRevisionType() {
|
||||
return revisionType;
|
||||
}
|
||||
|
||||
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 the fake relation.
|
||||
// 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, nestedData,
|
||||
revisionType == RevisionType.DEL ? null : owningEntity, null);
|
||||
|
||||
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) {
|
||||
/*
|
||||
* The merging rules are the following (revision types of the first and second work units):
|
||||
* - 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 (revisionType == RevisionType.DEL || second.getRevisionType() == RevisionType.ADD) {
|
||||
return second;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -30,7 +30,6 @@ import java.util.Map;
|
|||
import org.hibernate.envers.RevisionType;
|
||||
import org.hibernate.envers.configuration.AuditConfiguration;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.engine.SessionImplementor;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
||||
|
@ -54,12 +53,10 @@ public class ModWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
|
|||
return changes;
|
||||
}
|
||||
|
||||
public void perform(Session session, Object revisionData) {
|
||||
public Map<String, Object> generateData(Object revisionData) {
|
||||
fillDataWithId(data, revisionData, RevisionType.MOD);
|
||||
|
||||
session.save(verCfg.getAuditEntCfg().getAuditEntityName(getEntityName()), data);
|
||||
|
||||
setPerformed(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public Map<String, Object> getData() {
|
||||
|
@ -82,6 +79,10 @@ public class ModWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
|
|||
return this;
|
||||
}
|
||||
|
||||
public AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second) {
|
||||
return FakeBidirectionalRelationWorkUnit.merge(second, this, second.getNestedWorkUnit());
|
||||
}
|
||||
|
||||
public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
|
||||
return first.merge(this);
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ import org.hibernate.envers.configuration.AuditEntitiesConfiguration;
|
|||
import org.hibernate.envers.entities.mapper.PersistentCollectionChangeData;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.persister.collection.AbstractCollectionPersister;
|
||||
import org.hibernate.engine.CollectionEntry;
|
||||
import org.hibernate.engine.SessionImplementor;
|
||||
import org.hibernate.collection.PersistentCollection;
|
||||
|
@ -48,11 +47,11 @@ public class PersistentCollectionChangeWorkUnit extends AbstractAuditWorkUnit im
|
|||
|
||||
public PersistentCollectionChangeWorkUnit(SessionImplementor sessionImplementor, String entityName,
|
||||
AuditConfiguration auditCfg, PersistentCollection collection,
|
||||
CollectionEntry collectionEntry, Serializable snapshot, Serializable id) {
|
||||
CollectionEntry collectionEntry, Serializable snapshot, Serializable id,
|
||||
String referencingPropertyName) {
|
||||
super(sessionImplementor, entityName, auditCfg, new PersistentCollectionChangeWorkUnitId(id, collectionEntry.getRole()));
|
||||
|
||||
String ownerEntityName = ((AbstractCollectionPersister) collectionEntry.getLoadedPersister()).getOwnerEntityName();
|
||||
referencingPropertyName = collectionEntry.getRole().substring(ownerEntityName.length() + 1);
|
||||
this.referencingPropertyName = referencingPropertyName;
|
||||
|
||||
collectionChanges = auditCfg.getEntCfg().get(getEntityName()).getPropertyMapper()
|
||||
.mapCollectionChanges(referencingPropertyName, collection, snapshot, id);
|
||||
|
@ -72,6 +71,10 @@ public class PersistentCollectionChangeWorkUnit extends AbstractAuditWorkUnit im
|
|||
return collectionChanges != null && collectionChanges.size() != 0;
|
||||
}
|
||||
|
||||
public Map<String, Object> generateData(Object revisionData) {
|
||||
throw new UnsupportedOperationException("Cannot generate data for a collection change work unit!");
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public void perform(Session session, Object revisionData) {
|
||||
AuditEntitiesConfiguration entitiesCfg = verCfg.getAuditEntCfg();
|
||||
|
@ -109,6 +112,10 @@ public class PersistentCollectionChangeWorkUnit extends AbstractAuditWorkUnit im
|
|||
return null;
|
||||
}
|
||||
|
||||
public AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
|
||||
if (first instanceof PersistentCollectionChangeWorkUnit) {
|
||||
PersistentCollectionChangeWorkUnit original = (PersistentCollectionChangeWorkUnit) first;
|
||||
|
|
|
@ -32,4 +32,5 @@ public interface WorkUnitMergeVisitor {
|
|||
AuditWorkUnit merge(ModWorkUnit second);
|
||||
AuditWorkUnit merge(DelWorkUnit second);
|
||||
AuditWorkUnit merge(CollectionChangeWorkUnit second);
|
||||
AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second);
|
||||
}
|
||||
|
|
|
@ -12,4 +12,12 @@ public class MappingTools {
|
|||
public static String createComponentPrefix(String componentName) {
|
||||
return componentName + "_";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param referencePropertyName The name of the property that holds the relation to the entity.
|
||||
* @return A prefix which should be used to prefix an id mapper for the related entity.
|
||||
*/
|
||||
public static String createToOneRelationPrefix(String referencePropertyName) {
|
||||
return referencePropertyName + "_";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
package org.hibernate.envers.test.entities.onetomany.detached;
|
||||
|
||||
import org.hibernate.envers.Audited;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
/**
|
||||
* Entity for {@link org.hibernate.envers.test.integration.onetomany.detached.JoinColumnBidirectionalList} test.
|
||||
* Owned side of the relation.
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
*/
|
||||
@Entity
|
||||
@Audited
|
||||
public class ListJoinColumnBidirectionalRefEdEntity {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Integer id;
|
||||
|
||||
private String data;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "some_join_column", insertable = false, updatable = false)
|
||||
private ListJoinColumnBidirectionalRefIngEntity owner;
|
||||
|
||||
public ListJoinColumnBidirectionalRefEdEntity() { }
|
||||
|
||||
public ListJoinColumnBidirectionalRefEdEntity(Integer id, String data, ListJoinColumnBidirectionalRefIngEntity owner) {
|
||||
this.id = id;
|
||||
this.data = data;
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public ListJoinColumnBidirectionalRefEdEntity(String data, ListJoinColumnBidirectionalRefIngEntity owner) {
|
||||
this.data = data;
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public ListJoinColumnBidirectionalRefIngEntity getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(ListJoinColumnBidirectionalRefIngEntity owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ListJoinColumnBidirectionalRefEdEntity)) return false;
|
||||
|
||||
ListJoinColumnBidirectionalRefEdEntity that = (ListJoinColumnBidirectionalRefEdEntity) o;
|
||||
|
||||
if (data != null ? !data.equals(that.data) : that.data != null) return false;
|
||||
//noinspection RedundantIfStatement
|
||||
if (id != null ? !id.equals(that.id) : that.id != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int result;
|
||||
result = (id != null ? id.hashCode() : 0);
|
||||
result = 31 * result + (data != null ? data.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "ListJoinColumnBidirectionalRefEdEntity(id = " + id + ", data = " + data + ")";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package org.hibernate.envers.test.entities.onetomany.detached;
|
||||
|
||||
import org.hibernate.envers.Audited;
|
||||
import org.hibernate.envers.AuditMappedBy;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Entity for {@link org.hibernate.envers.test.integration.onetomany.detached.JoinColumnBidirectionalList} test.
|
||||
* Owning side of the relation.
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
*/
|
||||
@Entity
|
||||
@Audited
|
||||
public class ListJoinColumnBidirectionalRefIngEntity {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Integer id;
|
||||
|
||||
private String data;
|
||||
|
||||
@OneToMany
|
||||
@JoinColumn(name = "some_join_column")
|
||||
@AuditMappedBy(mappedBy = "owner")
|
||||
private List<ListJoinColumnBidirectionalRefEdEntity> references;
|
||||
|
||||
public ListJoinColumnBidirectionalRefIngEntity() { }
|
||||
|
||||
public ListJoinColumnBidirectionalRefIngEntity(Integer id, String data, ListJoinColumnBidirectionalRefEdEntity... references) {
|
||||
this.id = id;
|
||||
this.data = data;
|
||||
this.references = new ArrayList<ListJoinColumnBidirectionalRefEdEntity>();
|
||||
this.references.addAll(Arrays.asList(references));
|
||||
}
|
||||
|
||||
public ListJoinColumnBidirectionalRefIngEntity(String data, ListJoinColumnBidirectionalRefEdEntity... references) {
|
||||
this(null, data, references);
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public List<ListJoinColumnBidirectionalRefEdEntity> getReferences() {
|
||||
return references;
|
||||
}
|
||||
|
||||
public void setReferences(List<ListJoinColumnBidirectionalRefEdEntity> references) {
|
||||
this.references = references;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ListJoinColumnBidirectionalRefIngEntity)) return false;
|
||||
|
||||
ListJoinColumnBidirectionalRefIngEntity that = (ListJoinColumnBidirectionalRefIngEntity) o;
|
||||
|
||||
if (data != null ? !data.equals(that.data) : that.data != null) return false;
|
||||
//noinspection RedundantIfStatement
|
||||
if (id != null ? !id.equals(that.id) : that.id != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int result;
|
||||
result = (id != null ? id.hashCode() : 0);
|
||||
result = 31 * result + (data != null ? data.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "ListJoinColumnBidirectionalRefIngEntity(id = " + id + ", data = " + data + ")";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Middleware LLC.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.envers.test.integration.onetomany.detached;
|
||||
|
||||
import org.hibernate.ejb.Ejb3Configuration;
|
||||
import org.hibernate.envers.test.AbstractEntityTest;
|
||||
import org.hibernate.envers.test.entities.onetomany.detached.ListJoinColumnBidirectionalRefEdEntity;
|
||||
import org.hibernate.envers.test.entities.onetomany.detached.ListJoinColumnBidirectionalRefIngEntity;
|
||||
import static org.hibernate.envers.test.tools.TestTools.checkList;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Test for a "fake" bidirectional mapping where one side uses @OneToMany+@JoinColumn (and thus owns the relatin),
|
||||
* and the other uses a @ManyToOne(insertable=false, updatable=false).
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
*/
|
||||
public class JoinColumnBidirectionalList extends AbstractEntityTest {
|
||||
private Integer ed1_id;
|
||||
private Integer ed2_id;
|
||||
|
||||
private Integer ing1_id;
|
||||
private Integer ing2_id;
|
||||
|
||||
public void configure(Ejb3Configuration cfg) {
|
||||
cfg.addAnnotatedClass(ListJoinColumnBidirectionalRefIngEntity.class);
|
||||
cfg.addAnnotatedClass(ListJoinColumnBidirectionalRefEdEntity.class);
|
||||
}
|
||||
|
||||
@Test(enabled = true)
|
||||
public void createData() {
|
||||
EntityManager em = getEntityManager();
|
||||
|
||||
ListJoinColumnBidirectionalRefEdEntity ed1 = new ListJoinColumnBidirectionalRefEdEntity("ed1", null);
|
||||
ListJoinColumnBidirectionalRefEdEntity ed2 = new ListJoinColumnBidirectionalRefEdEntity("ed2", null);
|
||||
|
||||
ListJoinColumnBidirectionalRefIngEntity ing1 = new ListJoinColumnBidirectionalRefIngEntity("coll1", ed1);
|
||||
ListJoinColumnBidirectionalRefIngEntity ing2 = new ListJoinColumnBidirectionalRefIngEntity("coll1", ed2);
|
||||
|
||||
// Revision 1 (ing1: ed1, ing2: ed2)
|
||||
em.getTransaction().begin();
|
||||
|
||||
em.persist(ed1);
|
||||
em.persist(ed2);
|
||||
em.persist(ing1);
|
||||
em.persist(ing2);
|
||||
|
||||
em.getTransaction().commit();
|
||||
|
||||
// Revision 2 (ing1: ed1, ed2)
|
||||
em.getTransaction().begin();
|
||||
|
||||
ing1 = em.find(ListJoinColumnBidirectionalRefIngEntity.class, ing1.getId());
|
||||
ing2 = em.find(ListJoinColumnBidirectionalRefIngEntity.class, ing2.getId());
|
||||
ed1 = em.find(ListJoinColumnBidirectionalRefEdEntity.class, ed1.getId());
|
||||
ed2 = em.find(ListJoinColumnBidirectionalRefEdEntity.class, ed2.getId());
|
||||
|
||||
ing2.getReferences().remove(ed2);
|
||||
ing1.getReferences().add(ed2);
|
||||
|
||||
em.getTransaction().commit();
|
||||
em.clear();
|
||||
|
||||
// Revision 3 (ing1: ed1, ed2)
|
||||
em.getTransaction().begin();
|
||||
|
||||
ed1 = em.find(ListJoinColumnBidirectionalRefEdEntity.class, ed1.getId());
|
||||
|
||||
ed1.setData("ed1 bis");
|
||||
|
||||
em.getTransaction().commit();
|
||||
em.clear();
|
||||
|
||||
// Revision 4 (ing2: ed1, ed2)
|
||||
em.getTransaction().begin();
|
||||
|
||||
ing1 = em.find(ListJoinColumnBidirectionalRefIngEntity.class, ing1.getId());
|
||||
ing2 = em.find(ListJoinColumnBidirectionalRefIngEntity.class, ing2.getId());
|
||||
|
||||
ing1.getReferences().clear();
|
||||
ing2.getReferences().add(ed1);
|
||||
ing2.getReferences().add(ed2);
|
||||
|
||||
em.getTransaction().commit();
|
||||
em.clear();
|
||||
|
||||
//
|
||||
|
||||
ing1_id = ing1.getId();
|
||||
ing2_id = ing2.getId();
|
||||
|
||||
ed1_id = ed1.getId();
|
||||
ed2_id = ed2.getId();
|
||||
}
|
||||
|
||||
@Test(enabled = true, dependsOnMethods = "createData")
|
||||
public void testRevisionsCounts() {
|
||||
assertEquals(Arrays.asList(1, 2, 4), getAuditReader().getRevisions(ListJoinColumnBidirectionalRefIngEntity.class, ing1_id));
|
||||
assertEquals(Arrays.asList(1, 2, 4), getAuditReader().getRevisions(ListJoinColumnBidirectionalRefIngEntity.class, ing2_id));
|
||||
|
||||
assertEquals(Arrays.asList(1, 3, 4), getAuditReader().getRevisions(ListJoinColumnBidirectionalRefEdEntity.class, ed1_id));
|
||||
assertEquals(Arrays.asList(1, 2, 4), getAuditReader().getRevisions(ListJoinColumnBidirectionalRefEdEntity.class, ed2_id));
|
||||
}
|
||||
|
||||
@Test(enabled = true, dependsOnMethods = "createData")
|
||||
public void testHistoryOfIng1() {
|
||||
ListJoinColumnBidirectionalRefEdEntity ed1_fromRev1 = new ListJoinColumnBidirectionalRefEdEntity(ed1_id, "ed1", null);
|
||||
ListJoinColumnBidirectionalRefEdEntity ed1_fromRev3 = new ListJoinColumnBidirectionalRefEdEntity(ed1_id, "ed1 bis", null);
|
||||
ListJoinColumnBidirectionalRefEdEntity ed2 = getEntityManager().find(ListJoinColumnBidirectionalRefEdEntity.class, ed2_id);
|
||||
|
||||
ListJoinColumnBidirectionalRefIngEntity rev1 = getAuditReader().find(ListJoinColumnBidirectionalRefIngEntity.class, ing1_id, 1);
|
||||
ListJoinColumnBidirectionalRefIngEntity rev2 = getAuditReader().find(ListJoinColumnBidirectionalRefIngEntity.class, ing1_id, 2);
|
||||
ListJoinColumnBidirectionalRefIngEntity rev3 = getAuditReader().find(ListJoinColumnBidirectionalRefIngEntity.class, ing1_id, 3);
|
||||
ListJoinColumnBidirectionalRefIngEntity rev4 = getAuditReader().find(ListJoinColumnBidirectionalRefIngEntity.class, ing1_id, 4);
|
||||
|
||||
assertTrue(checkList(rev1.getReferences(), ed1_fromRev1));
|
||||
assertTrue(checkList(rev2.getReferences(), ed1_fromRev1, ed2));
|
||||
assertTrue(checkList(rev3.getReferences(), ed1_fromRev3, ed2));
|
||||
assertTrue(checkList(rev4.getReferences()));
|
||||
}
|
||||
|
||||
@Test(enabled = true, dependsOnMethods = "createData")
|
||||
public void testHistoryOfIng2() {
|
||||
ListJoinColumnBidirectionalRefEdEntity ed1 = getEntityManager().find(ListJoinColumnBidirectionalRefEdEntity.class, ed1_id);
|
||||
ListJoinColumnBidirectionalRefEdEntity ed2 = getEntityManager().find(ListJoinColumnBidirectionalRefEdEntity.class, ed2_id);
|
||||
|
||||
ListJoinColumnBidirectionalRefIngEntity rev1 = getAuditReader().find(ListJoinColumnBidirectionalRefIngEntity.class, ing2_id, 1);
|
||||
ListJoinColumnBidirectionalRefIngEntity rev2 = getAuditReader().find(ListJoinColumnBidirectionalRefIngEntity.class, ing2_id, 2);
|
||||
ListJoinColumnBidirectionalRefIngEntity rev3 = getAuditReader().find(ListJoinColumnBidirectionalRefIngEntity.class, ing2_id, 3);
|
||||
ListJoinColumnBidirectionalRefIngEntity rev4 = getAuditReader().find(ListJoinColumnBidirectionalRefIngEntity.class, ing2_id, 4);
|
||||
|
||||
assertTrue(checkList(rev1.getReferences(), ed2));
|
||||
assertTrue(checkList(rev2.getReferences()));
|
||||
assertTrue(checkList(rev3.getReferences()));
|
||||
assertTrue(checkList(rev4.getReferences(), ed1, ed2));
|
||||
}
|
||||
|
||||
@Test(enabled = true, dependsOnMethods = "createData")
|
||||
public void testHistoryOfEd1() {
|
||||
ListJoinColumnBidirectionalRefIngEntity ing1 = getEntityManager().find(ListJoinColumnBidirectionalRefIngEntity.class, ing1_id);
|
||||
ListJoinColumnBidirectionalRefIngEntity ing2 = getEntityManager().find(ListJoinColumnBidirectionalRefIngEntity.class, ing2_id);
|
||||
|
||||
ListJoinColumnBidirectionalRefEdEntity rev1 = getAuditReader().find(ListJoinColumnBidirectionalRefEdEntity.class, ed1_id, 1);
|
||||
ListJoinColumnBidirectionalRefEdEntity rev2 = getAuditReader().find(ListJoinColumnBidirectionalRefEdEntity.class, ed1_id, 2);
|
||||
ListJoinColumnBidirectionalRefEdEntity rev3 = getAuditReader().find(ListJoinColumnBidirectionalRefEdEntity.class, ed1_id, 3);
|
||||
ListJoinColumnBidirectionalRefEdEntity rev4 = getAuditReader().find(ListJoinColumnBidirectionalRefEdEntity.class, ed1_id, 4);
|
||||
|
||||
assertTrue(rev1.getOwner().equals(ing1));
|
||||
assertTrue(rev2.getOwner().equals(ing1));
|
||||
assertTrue(rev3.getOwner().equals(ing1));
|
||||
assertTrue(rev4.getOwner().equals(ing2));
|
||||
|
||||
assertEquals(rev1.getData(), "ed1");
|
||||
assertEquals(rev2.getData(), "ed1");
|
||||
assertEquals(rev3.getData(), "ed1 bis");
|
||||
assertEquals(rev4.getData(), "ed1 bis");
|
||||
}
|
||||
|
||||
@Test(enabled = true, dependsOnMethods = "createData")
|
||||
public void testHistoryOfEd2() {
|
||||
ListJoinColumnBidirectionalRefIngEntity ing1 = getEntityManager().find(ListJoinColumnBidirectionalRefIngEntity.class, ing1_id);
|
||||
ListJoinColumnBidirectionalRefIngEntity ing2 = getEntityManager().find(ListJoinColumnBidirectionalRefIngEntity.class, ing2_id);
|
||||
|
||||
ListJoinColumnBidirectionalRefEdEntity rev1 = getAuditReader().find(ListJoinColumnBidirectionalRefEdEntity.class, ed2_id, 1);
|
||||
ListJoinColumnBidirectionalRefEdEntity rev2 = getAuditReader().find(ListJoinColumnBidirectionalRefEdEntity.class, ed2_id, 2);
|
||||
ListJoinColumnBidirectionalRefEdEntity rev3 = getAuditReader().find(ListJoinColumnBidirectionalRefEdEntity.class, ed2_id, 3);
|
||||
ListJoinColumnBidirectionalRefEdEntity rev4 = getAuditReader().find(ListJoinColumnBidirectionalRefEdEntity.class, ed2_id, 4);
|
||||
|
||||
assertTrue(rev1.getOwner().equals(ing2));
|
||||
assertTrue(rev2.getOwner().equals(ing1));
|
||||
assertTrue(rev3.getOwner().equals(ing1));
|
||||
assertTrue(rev4.getOwner().equals(ing2));
|
||||
|
||||
assertEquals(rev1.getData(), "ed2");
|
||||
assertEquals(rev2.getData(), "ed2");
|
||||
assertEquals(rev3.getData(), "ed2");
|
||||
assertEquals(rev4.getData(), "ed2");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue