Author: adamw
Date: 2009-12-15 05:50:11 -0500 (Tue, 15 Dec 2009)
New Revision: 18224
Added:
core/trunk/envers/src/main/java/org/hibernate/envers/AuditMappedBy.java
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/FakeBidirectionalRelationWorkUnit.java
core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefEdEntity.java
core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefIngEntity.java
core/trunk/envers/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/JoinColumnBidirectionalList.java
Modified:
core/trunk/envers/pom.xml
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/EntitiesConfigurator.java
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/IdMetadataGenerator.java
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/ClassAuditingData.java
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java
core/trunk/envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java
core/trunk/envers/src/main/java/org/hibernate/envers/entities/RelationDescription.java
core/trunk/envers/src/main/java/org/hibernate/envers/event/AuditEventListener.java
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/AbstractAuditWorkUnit.java
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/AddWorkUnit.java
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/AuditWorkUnit.java
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/CollectionChangeWorkUnit.java
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/DelWorkUnit.java
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/ModWorkUnit.java
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/PersistentCollectionChangeWorkUnit.java
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/WorkUnitMergeVisitor.java
core/trunk/envers/src/main/java/org/hibernate/envers/tools/MappingTools.java
Log:
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
Modified: core/trunk/envers/pom.xml
===================================================================
--- core/trunk/envers/pom.xml 2009-12-13 01:17:08 UTC (rev 18223)
+++ core/trunk/envers/pom.xml 2009-12-15 10:50:11 UTC (rev 18224)
@@ -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>
Added: core/trunk/envers/src/main/java/org/hibernate/envers/AuditMappedBy.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/AuditMappedBy.java
(rev 0)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/AuditMappedBy.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -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)
+ */
+(a)Retention(RetentionPolicy.RUNTIME)
+(a)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 "";
+}
Added:
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java
(rev 0)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -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);
+ }
+}
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/EntitiesConfigurator.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/EntitiesConfigurator.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/EntitiesConfigurator.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -60,30 +60,36 @@
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 @@
}
// 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 @@
auditMetaGen.getNotAuditedEntitiesConfigurations());
}
- // todo
+ @SuppressWarnings({"UnusedDeclaration"})
private void writeDocument(Document e) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Writer w = new PrintWriter(baos);
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -30,6 +30,7 @@
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 @@
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 AuditMetadataGenerator(Configuration cfg, GlobalConfiguration globalCfg,
AuditEntitiesConfiguration verEntCfg,
Element revisionInfoRelationMapping,
- AuditEntityNameRegister auditEntityNameRegister) {
+ AuditEntityNameRegister auditEntityNameRegister,
+ ClassesAuditingData classesAuditingData) {
this.cfg = cfg;
this.globalCfg = globalCfg;
this.verEntCfg = verEntCfg;
@@ -89,7 +98,8 @@
this.idMetadataGenerator = new IdMetadataGenerator(this);
this.toOneRelationMetadataGenerator = new ToOneRelationMetadataGenerator(this);
- this.auditEntityNameRegister = auditEntityNameRegister;
+ this.auditEntityNameRegister = auditEntityNameRegister;
+ this.classesAuditingData = classesAuditingData;
entitiesConfigurations = new HashMap<String, EntityConfiguration>();
notAuditedEntitiesConfigurations = new HashMap<String,
EntityConfiguration>();
@@ -448,6 +458,10 @@
return entitiesConfigurations;
}
+ public ClassesAuditingData getClassesAuditingData() {
+ return classesAuditingData;
+ }
+
// Getters for generators and configuration
BasicMetadataGenerator getBasicMetadataGenerator() {
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -40,13 +40,11 @@
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.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.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 @@
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 @@
}
@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 @@
// 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 @@
@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 @@
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 @@
} 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,11 +533,35 @@
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 @@
@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();
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/IdMetadataGenerator.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/IdMetadataGenerator.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/IdMetadataGenerator.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -139,6 +139,6 @@
private PropertyAuditingData getIdPersistentPropertyAuditingData(Property property)
{
return new PropertyAuditingData(property.getName(),
property.getPropertyAccessorName(),
- ModificationStore.FULL, RelationTargetAuditMode.AUDITED);
+ ModificationStore.FULL, RelationTargetAuditMode.AUDITED, null, null);
}
}
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -32,17 +32,23 @@
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 @@
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 @@
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 @@
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
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -14,12 +14,7 @@
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 @@
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<? extends Annotation> annotationType() { return this.getClass(); }
};
- private class ComponentPropertiesSource implements PersistentPropertiesSource {
+ private class ComponentPropertiesSource implements PersistentPropertiesSource {
private final XClass xclass;
private final Component component;
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/ClassAuditingData.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/ClassAuditingData.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/ClassAuditingData.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -57,6 +57,10 @@
return properties.get(propertyName);
}
+ public Iterable<String> getPropertyNames() {
+ return properties.keySet();
+ }
+
public Map<String, String> getSecondaryTableDictionary() {
return secondaryTableDictionary;
}
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -46,17 +46,22 @@
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 @@
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;
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -27,6 +27,7 @@
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 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) {
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/entities/RelationDescription.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/entities/RelationDescription.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/entities/RelationDescription.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -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 @@
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 @@
return idMapper;
}
+ public PropertyMapper getFakeBidirectionalRelationMapper() {
+ return fakeBidirectionalRelationMapper;
+ }
+
public boolean isBidirectional() {
return bidirectional;
}
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/event/AuditEventListener.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/event/AuditEventListener.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/event/AuditEventListener.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -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.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.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 @@
}
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 @@
// 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 @@
}
}
+ 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 @@
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);
+ }
}
}
}
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/AbstractAuditWorkUnit.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/AbstractAuditWorkUnit.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/AbstractAuditWorkUnit.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -64,6 +64,14 @@
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;
}
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/AddWorkUnit.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/AddWorkUnit.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/AddWorkUnit.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -30,7 +30,6 @@
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 @@
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 @@
return this;
}
+ public AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second) {
+ return FakeBidirectionalRelationWorkUnit.merge(second, this,
second.getNestedWorkUnit());
+ }
+
public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
return first.merge(this);
}
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/AuditWorkUnit.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/AuditWorkUnit.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/AuditWorkUnit.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -25,7 +25,10 @@
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 {
@@ -35,7 +38,20 @@
boolean containsWork();
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);
}
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/CollectionChangeWorkUnit.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/CollectionChangeWorkUnit.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/CollectionChangeWorkUnit.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -30,7 +30,6 @@
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.configuration.AuditConfiguration;
-import org.hibernate.Session;
import org.hibernate.engine.SessionImplementor;
/**
@@ -50,16 +49,14 @@
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 @@
return this;
}
+ public AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second) {
+ return second;
+ }
+
public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
return first.merge(this);
}
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/DelWorkUnit.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/DelWorkUnit.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/DelWorkUnit.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -30,7 +30,6 @@
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 @@
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 @@
propertyNames, state, state);
}
- session.save(verCfg.getAuditEntCfg().getAuditEntityName(getEntityName()), data);
-
- setPerformed(data);
+ return data;
}
public AuditWorkUnit merge(AddWorkUnit second) {
@@ -83,6 +80,10 @@
return this;
}
+ public AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second) {
+ return this;
+ }
+
public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
return first.merge(this);
}
Added:
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/FakeBidirectionalRelationWorkUnit.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/FakeBidirectionalRelationWorkUnit.java
(rev 0)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/FakeBidirectionalRelationWorkUnit.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -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);
+ }
+}
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/ModWorkUnit.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/ModWorkUnit.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/ModWorkUnit.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -30,7 +30,6 @@
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 @@
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 @@
return this;
}
+ public AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second) {
+ return FakeBidirectionalRelationWorkUnit.merge(second, this,
second.getNestedWorkUnit());
+ }
+
public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
return first.merge(this);
}
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/PersistentCollectionChangeWorkUnit.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/PersistentCollectionChangeWorkUnit.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/PersistentCollectionChangeWorkUnit.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -34,7 +34,6 @@
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 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 @@
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 @@
return null;
}
+ public AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second) {
+ return null;
+ }
+
public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
if (first instanceof PersistentCollectionChangeWorkUnit) {
PersistentCollectionChangeWorkUnit original =
(PersistentCollectionChangeWorkUnit) first;
Modified:
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/WorkUnitMergeVisitor.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/WorkUnitMergeVisitor.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/WorkUnitMergeVisitor.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -32,4 +32,5 @@
AuditWorkUnit merge(ModWorkUnit second);
AuditWorkUnit merge(DelWorkUnit second);
AuditWorkUnit merge(CollectionChangeWorkUnit second);
+ AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second);
}
Modified: core/trunk/envers/src/main/java/org/hibernate/envers/tools/MappingTools.java
===================================================================
---
core/trunk/envers/src/main/java/org/hibernate/envers/tools/MappingTools.java 2009-12-13
01:17:08 UTC (rev 18223)
+++
core/trunk/envers/src/main/java/org/hibernate/envers/tools/MappingTools.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -12,4 +12,12 @@
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 + "_";
+ }
}
Added:
core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefEdEntity.java
===================================================================
---
core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefEdEntity.java
(rev 0)
+++
core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefEdEntity.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -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 + ")";
+ }
+}
Added:
core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefIngEntity.java
===================================================================
---
core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefIngEntity.java
(rev 0)
+++
core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefIngEntity.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -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 + ")";
+ }
+}
Added:
core/trunk/envers/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/JoinColumnBidirectionalList.java
===================================================================
---
core/trunk/envers/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/JoinColumnBidirectionalList.java
(rev 0)
+++
core/trunk/envers/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/JoinColumnBidirectionalList.java 2009-12-15
10:50:11 UTC (rev 18224)
@@ -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");
+ }
+}