[hibernate-commits] Hibernate SVN: r18237 - in core/branches/envers-hibernate-3.3: src/main/java/org/hibernate/envers and 15 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Wed Dec 16 06:30:20 EST 2009


Author: adamw
Date: 2009-12-16 06:30:19 -0500 (Wed, 16 Dec 2009)
New Revision: 18237

Added:
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/AuditMappedBy.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/FakeBidirectionalRelationWorkUnit.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/WorkUnitMergeDispatcher.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/WorkUnitMergeVisitor.java
   core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/DoubleListJoinColumnBidirectionalRefEdEntity1.java
   core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/DoubleListJoinColumnBidirectionalRefEdEntity2.java
   core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/DoubleListJoinColumnBidirectionalRefIngEntity.java
   core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefEdEntity.java
   core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefIngEntity.java
   core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefEdEntity.java
   core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefIngEntity.java
   core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/DoubleJoinColumnBidirectionalList.java
   core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/IndexedJoinColumnBidirectionalList.java
   core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/JoinColumnBidirectionalList.java
Removed:
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/KeepCheckDispatcher.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/KeepCheckResult.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/KeepCheckVisitor.java
Modified:
   core/branches/envers-hibernate-3.3/pom.xml
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/EntitiesConfigurator.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/BasicMetadataGenerator.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/IdMetadataGenerator.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/reader/ClassAuditingData.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/RelationDescription.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/PersistentCollectionChangeData.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/SinglePropertyMapper.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/ToOneIdMapper.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/event/AuditEventListener.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/AuditSync.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/AbstractAuditWorkUnit.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/AddWorkUnit.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/AuditWorkUnit.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/CollectionChangeWorkUnit.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/DelWorkUnit.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/ModWorkUnit.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/PersistentCollectionChangeWorkUnit.java
   core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/tools/MappingTools.java
   core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/flush/DoubleFlushAddMod.java
   core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/flush/DoubleFlushModDel.java
   core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/flush/DoubleFlushModMod.java
   core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/flush/ManualFlush.java
   core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/manytomany/unidirectional/M2MRelationNotAuditedTarget.java
Log:
svn merge -r 18202:18236 https://svn.jboss.org/repos/hibernate/core/trunk/envers .

Modified: core/branches/envers-hibernate-3.3/pom.xml
===================================================================
--- core/branches/envers-hibernate-3.3/pom.xml	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/pom.xml	2009-12-16 11:30:19 UTC (rev 18237)
@@ -70,8 +70,8 @@
             <artifactId>hibernate-core</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.hibernate.java-persistence</groupId>
-            <artifactId>jpa-api</artifactId>
+            <groupId>org.hibernate.javax.persistence</groupId>
+            <artifactId>hibernate-jpa-2.0-api</artifactId>
         </dependency>
         <dependency>
             <groupId>org.hibernate</groupId>
@@ -104,7 +104,7 @@
         <dependency>
             <groupId>com.h2database</groupId>
             <artifactId>h2</artifactId>
-            <version>1.0.79</version>
+            <version>1.2.125</version>
             <scope>test</scope>
         </dependency>
         <dependency>

Copied: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/AuditMappedBy.java (from rev 18236, core/trunk/envers/src/main/java/org/hibernate/envers/AuditMappedBy.java)
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/AuditMappedBy.java	                        (rev 0)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/AuditMappedBy.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -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)
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at 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 "";
+}

Copied: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java (from rev 18236, core/trunk/envers/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java)
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java	                        (rev 0)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -0,0 +1,96 @@
+package org.hibernate.envers.configuration;
+
+import org.hibernate.envers.configuration.metadata.reader.ClassAuditingData;
+import org.hibernate.envers.configuration.metadata.reader.PropertyAuditingData;
+import org.hibernate.envers.tools.MappingTools;
+import org.hibernate.mapping.PersistentClass;
+import org.hibernate.MappingException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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 static final Logger log = LoggerFactory.getLogger(ClassesAuditingData.class);
+
+    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);
+    }
+
+    /**
+     * After all meta-data is read, updates calculated fields. This includes:
+     * <ul>
+     * <li>setting {@code forceInsertable} to {@code true} for properties specified by {@code @AuditMappedBy}</li> 
+     * </ul>
+     */
+    public void updateCalculatedFields() {
+        for (Map.Entry<PersistentClass, ClassAuditingData> classAuditingDataEntry : persistentClassToAuditingData.entrySet()) {
+            PersistentClass pc = classAuditingDataEntry.getKey();
+            ClassAuditingData classAuditingData = classAuditingDataEntry.getValue();
+            for (String propertyName : classAuditingData.getPropertyNames()) {
+                PropertyAuditingData propertyAuditingData = classAuditingData.getPropertyAuditingData(propertyName);
+                // If a property had the @AuditMappedBy annotation, setting the referenced fields to be always insertable.
+                if (propertyAuditingData.getAuditMappedBy() != null) {
+                    String referencedEntityName = MappingTools.getReferencedEntityName(pc.getProperty(propertyName).getValue());
+
+                    ClassAuditingData referencedClassAuditingData = entityNameToAuditingData.get(referencedEntityName);
+
+                    forcePropertyInsertable(referencedClassAuditingData, propertyAuditingData.getAuditMappedBy(),
+                            pc.getEntityName(), referencedEntityName);
+
+                    forcePropertyInsertable(referencedClassAuditingData, propertyAuditingData.getPositionMappedBy(),
+                            pc.getEntityName(), referencedEntityName);
+                }
+            }
+        }
+    }
+
+    private void forcePropertyInsertable(ClassAuditingData classAuditingData, String propertyName,
+                                         String entityName, String referencedEntityName) {
+        if (propertyName != null) {
+            if (classAuditingData.getPropertyAuditingData(propertyName) == null) {
+                throw new MappingException("@AuditMappedBy points to a property that doesn't exist: " +
+                    referencedEntityName + "." + propertyName);
+            }
+
+            log.debug("Non-insertable property " + referencedEntityName + "." + propertyName +
+                    " will be made insertable because a matching @AuditMappedBy was found in the " +
+                    entityName + " entity.");
+
+            classAuditingData
+                    .getPropertyAuditingData(propertyName)
+                    .setForceInsertable(true);
+        }
+    }
+}

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/EntitiesConfigurator.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/EntitiesConfigurator.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/EntitiesConfigurator.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -60,30 +60,39 @@
                                             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);
+        }
+
+        // Now that all information is read we can update the calculated fields.
+        classesAuditingData.updateCalculatedFields();
+
+        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 +106,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 +141,7 @@
 				auditMetaGen.getNotAuditedEntitiesConfigurations());
     }
 
-    // todo
+    @SuppressWarnings({"UnusedDeclaration"})
     private void writeDocument(Document e) {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         Writer w = new PrintWriter(baos);

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -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>();
@@ -485,6 +495,10 @@
         return entitiesConfigurations;
     }
 
+    public ClassesAuditingData getClassesAuditingData() {
+        return classesAuditingData;
+    }
+
     // Getters for generators and configuration
 
     BasicMetadataGenerator getBasicMetadataGenerator() {

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/BasicMetadataGenerator.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/BasicMetadataGenerator.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/BasicMetadataGenerator.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -66,7 +66,7 @@
 								Value value, SimpleMapperBuilder mapper, boolean insertable, boolean key) {
 		if (parent != null) {
 			Element prop_mapping = MetadataTools.addProperty(parent, propertyAuditingData.getName(),
-					value.getType().getName(), insertable, key);
+					value.getType().getName(), propertyAuditingData.isForceInsertable() || insertable, key);
 			MetadataTools.addColumns(prop_mapping, (Iterator<Column>) value.getColumnIterator());
 		}
 

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -40,18 +40,13 @@
 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.relation.component.MiddleDummyComponentMapper;
-import org.hibernate.envers.entities.mapper.relation.component.MiddleMapKeyIdComponentMapper;
-import org.hibernate.envers.entities.mapper.relation.component.MiddleMapKeyPropertyComponentMapper;
-import org.hibernate.envers.entities.mapper.relation.component.MiddleRelatedComponentMapper;
-import org.hibernate.envers.entities.mapper.relation.component.MiddleSimpleComponentMapper;
+import org.hibernate.envers.entities.mapper.PropertyMapper;
+import org.hibernate.envers.entities.mapper.SinglePropertyMapper;
+import org.hibernate.envers.entities.mapper.id.IdMapper;
+import org.hibernate.envers.entities.mapper.relation.*;
+import org.hibernate.envers.entities.mapper.relation.component.*;
 import org.hibernate.envers.entities.mapper.relation.lazy.proxy.ListProxy;
 import org.hibernate.envers.entities.mapper.relation.lazy.proxy.MapProxy;
 import org.hibernate.envers.entities.mapper.relation.lazy.proxy.SetProxy;
@@ -61,6 +56,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;
@@ -69,7 +65,6 @@
 import org.hibernate.mapping.PersistentClass;
 import org.hibernate.mapping.Property;
 import org.hibernate.mapping.Table;
-import org.hibernate.mapping.ToOne;
 import org.hibernate.mapping.Value;
 import org.hibernate.type.BagType;
 import org.hibernate.type.ListType;
@@ -79,12 +74,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;
@@ -109,7 +108,7 @@
      * be created using this object.
      * @param propertyAuditingData Property auditing (meta-)data. Among other things, holds the name of the
      * property that references the collection in the referencing entity, the user data for middle (join)
-     * table and the value of the <code>@MapKey</code> annotation, if there was one. 
+     * table and the value of the <code>@MapKey</code> annotation, if there was one.
      */
     public CollectionMetadataGenerator(AuditMetadataGenerator mainGenerator,
                                        Collection propertyValue, CompositeMapperBuilder currentMapper,
@@ -129,26 +128,19 @@
             throw new MappingException("Unable to read auditing configuration for " + referencingEntityName + "!");
         }
 
-        referencedEntityName = getReferencedEntityName(propertyValue.getElement());
+        referencedEntityName = MappingTools.getReferencedEntityName(propertyValue.getElement());
     }
 
-    private String getReferencedEntityName(Value value) {
-        if (value instanceof ToOne) {
-            return ((ToOne) value).getReferencedEntityName();
-        } else if (value instanceof OneToMany) {
-            return ((OneToMany) value).getReferencedEntityName();
-        } else {
-            return null;
-        }
-    }
-
     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 +153,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,
@@ -197,12 +192,46 @@
                 propertyAuditingData.getPropertyData(),
                 referencingIdData, queryGenerator);
 
+        PropertyMapper fakeBidirectionalRelationMapper;
+        PropertyMapper fakeBidirectionalRelationIndexMapper;
+        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, false);
+
+            // Checking if there's an index defined. If so, adding a mapper for it.
+            if (propertyAuditingData.getPositionMappedBy() != null) {
+                String positionMappedBy = propertyAuditingData.getPositionMappedBy();
+                fakeBidirectionalRelationIndexMapper = new SinglePropertyMapper(new PropertyData(positionMappedBy, null, null, null));
+
+                // Also, overwriting the index component data to properly read the index.
+                indexComponentData = new MiddleComponentData(new MiddleStraightComponentMapper(positionMappedBy), 0);
+            } else {
+                fakeBidirectionalRelationIndexMapper = null;
+            }
+        } else {
+            fakeBidirectionalRelationMapper = null;
+            fakeBidirectionalRelationIndexMapper = null;
+        }
+
         // Checking the type of the collection and adding an appropriate mapper.
         addMapper(commonCollectionMapperData, elementComponentData, indexComponentData);
 
         // Storing information about this relation.
         referencingEntityConfiguration.addToManyNotOwningRelation(propertyName, mappedBy,
-                referencedEntityName, referencingIdData.getPrefixedMapper());
+                referencedEntityName, referencingIdData.getPrefixedMapper(), fakeBidirectionalRelationMapper,
+                fakeBidirectionalRelationIndexMapper);
     }
 
     /**
@@ -228,7 +257,7 @@
         if (value.getElement() instanceof OneToMany && !value.isInverse()) {
             // This must be a @JoinColumn+ at OneToMany mapping. Generating the table name, as Hibernate doesn't use a
             // middle table for mapping this relation.
-            return StringTools.getLastComponent(entityName) + "_" + StringTools.getLastComponent(getReferencedEntityName(value.getElement()));
+            return StringTools.getLastComponent(entityName) + "_" + StringTools.getLastComponent(MappingTools.getReferencedEntityName(value.getElement()));
         } else {
             // Hibernate uses a middle table for mapping this relation, so we get it's name directly.
             return value.getCollectionTable().getName();
@@ -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,16 +281,18 @@
             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;
-        if (!propertyValue.isInverse()) {            
+        if (!propertyValue.isInverse()) {
             // Generating a unique middle entity name
             auditMiddleEntityName = mainGenerator.getAuditEntityNameRegister().createUnique(auditMiddleEntityName);
 
             // Registering the generated name
             mainGenerator.getAuditEntityNameRegister().register(auditMiddleEntityName);
-                        
+
             middleEntityXml = createMiddleEntityXml(auditMiddleTableName, auditMiddleEntityName, propertyValue.getWhere());
         } else {
             middleEntityXml = null;
@@ -387,7 +421,7 @@
         if (type instanceof ManyToOneType) {
             String prefixRelated = prefix + "_";
 
-            String referencedEntityName = getReferencedEntityName(value);
+            String referencedEntityName = MappingTools.getReferencedEntityName(value);
 
             IdMappingData referencedIdMapping = mainGenerator.getReferencedIdMappingData(referencingEntityName,
                     referencedEntityName, propertyAuditingData, true);
@@ -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, false),
+                    value, null, true, true);
 
             if (mapped) {
                 // Simple values are always stored in the first item of the array returned by the query generator.
@@ -501,9 +535,16 @@
 
     @SuppressWarnings({"unchecked"})
     private String getMappedBy(Collection collectionValue) {
-        Iterator<Property> assocClassProps =
-                ((OneToMany) collectionValue.getElement()).getAssociatedClass().getPropertyIterator();
+        PersistentClass referencedClass = ((OneToMany) collectionValue.getElement()).getAssociatedClass();
 
+        // If there's an @AuditMappedBy specified, returning it directly.
+        String auditMappedBy = propertyAuditingData.getAuditMappedBy();
+        if (auditMappedBy != null) {
+            return auditMappedBy;
+        }
+
+        Iterator<Property> assocClassProps = referencedClass.getPropertyIterator();
+
         while (assocClassProps.hasNext()) {
             Property property = assocClassProps.next();
 
@@ -519,6 +560,12 @@
 
     @SuppressWarnings({"unchecked"})
     private String getMappedBy(Table collectionTable, PersistentClass referencedClass) {
+        // If there's an @AuditMappedBy specified, returning it directly.
+        String auditMappedBy = propertyAuditingData.getAuditMappedBy();
+        if (auditMappedBy != null) {
+            return auditMappedBy;
+        }
+
         Iterator<Property> properties = referencedClass.getPropertyIterator();
         while (properties.hasNext()) {
             Property property = properties.next();

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/IdMetadataGenerator.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/IdMetadataGenerator.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/IdMetadataGenerator.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -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, false);
     }
 }

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -24,6 +24,8 @@
 package org.hibernate.envers.configuration.metadata;
 
 import org.dom4j.Element;
+import org.hibernate.MappingException;
+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;
@@ -31,9 +33,7 @@
 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.MappingException;
+import org.hibernate.envers.tools.MappingTools;
 import org.hibernate.mapping.OneToOne;
 import org.hibernate.mapping.ToOne;
 import org.hibernate.mapping.Value;
@@ -57,15 +57,30 @@
         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);
 
         // Storing information about this relation
         mainGenerator.getEntitiesConfigurations().get(entityName).addToOneRelation(
-                propertyAuditingData.getName(), referencedEntityName, relMapper);
+                propertyAuditingData.getName(), referencedEntityName, relMapper, insertable);
 
+        // 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.
+        boolean nonInsertableFake;
+        if (!insertable && propertyAuditingData.isForceInsertable()) {
+            nonInsertableFake = true;
+            insertable = true;
+        } else {
+            nonInsertableFake = false;
+        }
+
         // Adding an element to the mapping corresponding to the references entity id's
         Element properties = (Element) idMapping.getXmlRelationMapping().clone();
         properties.addAttribute("name", propertyAuditingData.getName());
@@ -76,7 +91,7 @@
 
         // Adding mapper for the id
         PropertyData propertyData = propertyAuditingData.getPropertyData();
-        mapper.addComposite(propertyData, new ToOneIdMapper(relMapper, propertyData, referencedEntityName));
+        mapper.addComposite(propertyData, new ToOneIdMapper(relMapper, propertyData, referencedEntityName, nonInsertableFake));
     }
 
     @SuppressWarnings({"unchecked"})
@@ -97,7 +112,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/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -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/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/reader/ClassAuditingData.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/reader/ClassAuditingData.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/reader/ClassAuditingData.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -57,6 +57,10 @@
         return properties.get(propertyName);
     }
 
+    public Iterable<String> getPropertyNames() {
+        return properties.keySet();
+    }
+
     public Map<String, String> getSecondaryTableDictionary() {
         return secondaryTableDictionary;
     }

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -46,17 +46,25 @@
     private String accessType;
     private final List<AuditOverride> auditJoinTableOverrides = new ArrayList<AuditOverride>(0);
 	private RelationTargetAuditMode relationTargetAuditMode;
+    private String auditMappedBy;
+    private String positionMappedBy;
+    private boolean forceInsertable;
 
 	public PropertyAuditingData() {
     }
 
     public PropertyAuditingData(String name, String accessType, ModificationStore store,
-								RelationTargetAuditMode relationTargetAuditMode) {
+								RelationTargetAuditMode relationTargetAuditMode,
+                                String auditMappedBy, String positionMappedBy,
+                                boolean forceInsertable) {
         this.name = name;
 		this.beanName = name;
         this.accessType = accessType;
         this.store = store;
 		this.relationTargetAuditMode = relationTargetAuditMode;
+        this.auditMappedBy = auditMappedBy;
+        this.positionMappedBy = positionMappedBy;
+        this.forceInsertable = forceInsertable;
     }
 
 	public String getName() {
@@ -115,7 +123,31 @@
 		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 boolean isForceInsertable() {
+        return forceInsertable;
+    }
+
+    public void setForceInsertable(boolean forceInsertable) {
+        this.forceInsertable = forceInsertable;
+    }
+
+    public void addAuditingOverride(AuditOverride annotation) {
 		if (annotation != null) {
 			String overrideName = annotation.name();
 			boolean present = false;

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -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;
 
 /**
@@ -50,31 +51,33 @@
         this.relations = new HashMap<String, RelationDescription>();
     }
 
-    public void addToOneRelation(String fromPropertyName, String toEntityName, IdMapper idMapper) {
+    public void addToOneRelation(String fromPropertyName, String toEntityName, IdMapper idMapper, boolean insertable) {
         relations.put(fromPropertyName, new RelationDescription(fromPropertyName, RelationType.TO_ONE,
-                toEntityName, null, idMapper));
+                toEntityName, null, idMapper, null, null, insertable));
     }
 
     public void addToOneNotOwningRelation(String fromPropertyName, String mappedByPropertyName, String toEntityName,
-                                    IdMapper idMapper) {
+                                          IdMapper idMapper) {
         relations.put(fromPropertyName, new RelationDescription(fromPropertyName, RelationType.TO_ONE_NOT_OWNING,
-                toEntityName, mappedByPropertyName, idMapper));
+                toEntityName, mappedByPropertyName, idMapper, null, null, true));
     }
 
     public void addToManyNotOwningRelation(String fromPropertyName, String mappedByPropertyName, String toEntityName,
-                                     IdMapper idMapper) {
+                                           IdMapper idMapper, PropertyMapper fakeBidirectionalRelationMapper,
+                                           PropertyMapper fakeBidirectionalRelationIndexMapper) {
         relations.put(fromPropertyName, new RelationDescription(fromPropertyName, RelationType.TO_MANY_NOT_OWNING,
-                toEntityName, mappedByPropertyName, idMapper));
+                toEntityName, mappedByPropertyName, idMapper, fakeBidirectionalRelationMapper,
+                fakeBidirectionalRelationIndexMapper, true));
     }
 
     public void addToManyMiddleRelation(String fromPropertyName, String toEntityName) {
         relations.put(fromPropertyName, new RelationDescription(fromPropertyName, RelationType.TO_MANY_MIDDLE,
-                toEntityName, null, null));
+                toEntityName, null, null, null, null, true));
     }
 
     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, null, true));
     }
 
     public boolean isRelation(String propertyName) {

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/RelationDescription.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/RelationDescription.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/RelationDescription.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -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,23 @@
     private final String toEntityName;
     private final String mappedByPropertyName;
     private final IdMapper idMapper;
+    private final PropertyMapper fakeBidirectionalRelationMapper;
+    private final PropertyMapper fakeBidirectionalRelationIndexMapper;
+    private final boolean insertable;
     private boolean bidirectional;
 
     public RelationDescription(String fromPropertyName, RelationType relationType, String toEntityName,
-                               String mappedByPropertyName, IdMapper idMapper) {
+                               String mappedByPropertyName, IdMapper idMapper,
+                               PropertyMapper fakeBidirectionalRelationMapper,
+                               PropertyMapper fakeBidirectionalRelationIndexMapper, boolean insertable) {
         this.fromPropertyName = fromPropertyName;
         this.relationType = relationType;
         this.toEntityName = toEntityName;
         this.mappedByPropertyName = mappedByPropertyName;
         this.idMapper = idMapper;
+        this.fakeBidirectionalRelationMapper = fakeBidirectionalRelationMapper;
+        this.fakeBidirectionalRelationIndexMapper = fakeBidirectionalRelationIndexMapper;
+        this.insertable = insertable;
 
         this.bidirectional = false;
     }
@@ -67,6 +76,18 @@
         return idMapper;
     }
 
+    public PropertyMapper getFakeBidirectionalRelationMapper() {
+        return fakeBidirectionalRelationMapper;
+    }
+
+    public PropertyMapper getFakeBidirectionalRelationIndexMapper() {
+        return fakeBidirectionalRelationIndexMapper;
+    }
+
+    public boolean isInsertable() {
+        return insertable;
+    }
+
     public boolean isBidirectional() {
         return bidirectional;
     }

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/PersistentCollectionChangeData.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/PersistentCollectionChangeData.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/PersistentCollectionChangeData.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -23,10 +23,12 @@
  */
 package org.hibernate.envers.entities.mapper;
 
+import org.hibernate.envers.tools.Pair;
+
 import java.util.Map;
 
 /**
- * Data describing the change of a single object in a persisten collection (when the object was added, removed or
+ * Data describing the change of a single object in a persistent collection (when the object was added, removed or
  * modified in the collection).
  * @author Adam Warski (adam at warski dot org)
  */
@@ -54,10 +56,32 @@
     }
 
     /**
-     * For use by bi-directional associations.
      * @return The affected element, which was changed (added, removed, modified) in the collection.
      */
     public Object getChangedElement() {
+        if (changedElement instanceof Pair) {
+            return ((Pair) changedElement).getSecond();
+        }
+
+        if (changedElement instanceof Map.Entry) {
+            return ((Map.Entry) changedElement).getValue();
+        }
+
         return changedElement;
     }
+
+    /**
+     * @return Index of the affected element, or {@code null} if the collection isn't indexed.
+     */
+    public Object getChangedElementIndex() {
+        if (changedElement instanceof Pair) {
+            return ((Pair) changedElement).getFirst();
+        }
+
+        if (changedElement instanceof Map.Entry) {
+            return ((Map.Entry) changedElement).getKey();
+        }
+
+        return null;
+    }
 }

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/SinglePropertyMapper.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/SinglePropertyMapper.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/SinglePropertyMapper.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -47,6 +47,10 @@
 public class SinglePropertyMapper implements PropertyMapper, SimpleMapperBuilder {
     private PropertyData propertyData;
 
+    public SinglePropertyMapper(PropertyData propertyData) {
+        this.propertyData = propertyData;
+    }
+
     public SinglePropertyMapper() { }
 
     public void add(PropertyData propertyData) {

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -70,7 +70,6 @@
 
     protected abstract Collection getNewCollectionContent(PersistentCollection newCollection);
     protected abstract Collection getOldCollectionContent(Serializable oldCollection);
-    protected abstract Object getElement(Object changedObject);
 
     /**
      * Maps the changed collection element to the given map.
@@ -87,7 +86,7 @@
             entityData.put(commonCollectionMapperData.getVerEntCfg().getOriginalIdPropName(), originalId);
 
             collectionChanges.add(new PersistentCollectionChangeData(
-                    commonCollectionMapperData.getVersionsMiddleEntityName(), entityData, getElement(changedObj)));
+                    commonCollectionMapperData.getVersionsMiddleEntityName(), entityData, changedObj));
             // Mapping the collection owner's id.
             commonCollectionMapperData.getReferencingIdData().getPrefixedMapper().mapToMapFromId(originalId, id);
 

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -71,8 +71,4 @@
     protected void mapToMapFromObject(Map<String, Object> data, Object changed) {
         elementComponentData.getComponentMapper().mapToMapFromObject(data, changed);
     }
-
-    protected Object getElement(Object changedObject) {
-        return changedObject;
-    }
 }

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -83,9 +83,4 @@
         elementComponentData.getComponentMapper().mapToMapFromObject(data, indexValuePair.getSecond());
         indexComponentData.getComponentMapper().mapToMapFromObject(data, indexValuePair.getFirst());
     }
-
-    @SuppressWarnings({"unchecked"})
-    protected Object getElement(Object changedObject) {
-        return ((Pair<Integer, Object>) changedObject).getFirst();
-    }
 }
\ No newline at end of file

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -76,8 +76,4 @@
         elementComponentData.getComponentMapper().mapToMapFromObject(data, ((Map.Entry) changed).getValue());
         indexComponentData.getComponentMapper().mapToMapFromObject(data, ((Map.Entry) changed).getKey());
     }
-
-    protected Object getElement(Object changedObject) {
-        return ((Map.Entry) changedObject).getValue();
-    }
 }
\ No newline at end of file

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/ToOneIdMapper.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/ToOneIdMapper.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/ToOneIdMapper.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -49,20 +49,26 @@
     private final IdMapper delegate;
     private final PropertyData propertyData;
     private final String referencedEntityName;
+    private final boolean nonInsertableFake;
 
-    public ToOneIdMapper(IdMapper delegate, PropertyData propertyData, String referencedEntityName) {
+    public ToOneIdMapper(IdMapper delegate, PropertyData propertyData, String referencedEntityName, boolean nonInsertableFake) {
         this.delegate = delegate;
         this.propertyData = propertyData;
         this.referencedEntityName = referencedEntityName;
+        this.nonInsertableFake = nonInsertableFake;
     }
 
     public boolean mapToMapFromEntity(SessionImplementor session, Map<String, Object> data, Object newObj, Object oldObj) {
         HashMap<String, Object> newData = new HashMap<String, Object>();
         data.put(propertyData.getName(), newData);
 
-        delegate.mapToMapFromEntity(newData, newObj);
+        // If this property is originally non-insertable, but made insertable because it is in a many-to-one "fake"
+        // bi-directional relation, we always store the "old", unchaged data, to prevent storing changes made
+        // to this field. It is the responsibility of the collection to properly update it if it really changed.
+        delegate.mapToMapFromEntity(newData, nonInsertableFake ? oldObj : newObj);
 
-        return !Tools.entitiesEqual(session, newObj, oldObj);
+        //noinspection SimplifiableConditionalExpression
+        return nonInsertableFake ? false : !Tools.entitiesEqual(session, newObj, oldObj);
     }
 
     public void mapToEntityFromMap(AuditConfiguration verCfg, Object obj, Map data, Object primaryKey,

Copied: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java (from rev 18236, core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java)
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java	                        (rev 0)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -0,0 +1,56 @@
+/*
+ * 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.entities.mapper.relation.component;
+
+import org.hibernate.envers.entities.EntityInstantiator;
+import org.hibernate.envers.tools.query.Parameters;
+
+import java.util.Map;
+
+/**
+ * A mapper for reading and writing a property straight to/from maps. This mapper cannot be used with middle tables,
+ * but only with "fake" bidirectional indexed relations. 
+ * @author Adam Warski (adam at warski dot org)
+ */
+public final class MiddleStraightComponentMapper implements MiddleComponentMapper {
+    private final String propertyName;
+
+    public MiddleStraightComponentMapper(String propertyName) {
+        this.propertyName = propertyName;
+    }
+
+    @SuppressWarnings({"unchecked"})
+    public Object mapToObjectFromFullMap(EntityInstantiator entityInstantiator, Map<String, Object> data,
+                                         Object dataObject, Number revision) {
+        return data.get(propertyName);
+    }
+
+    public void mapToMapFromObject(Map<String, Object> data, Object obj) {
+        data.put(propertyName, obj);
+    }
+
+    public void addMiddleEqualToQuery(Parameters parameters, String prefix1, String prefix2) {
+        throw new UnsupportedOperationException("Cannot use this mapper with a middle table!");
+    }
+}
\ No newline at end of file

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/event/AuditEventListener.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/event/AuditEventListener.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/event/AuditEventListener.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -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;
@@ -86,7 +84,8 @@
         for (int i=0; i<propertyNames.length; i++) {
             String propertyName = propertyNames[i];
             RelationDescription relDesc = verCfg.getEntCfg().getRelationDescription(entityName, propertyName);
-            if (relDesc != null && relDesc.isBidirectional() && relDesc.getRelationType() == RelationType.TO_ONE) {
+            if (relDesc != null && relDesc.isBidirectional() && relDesc.getRelationType() == RelationType.TO_ONE &&
+                    relDesc.isInsertable()) {
                 // Checking for changes
                 Object oldValue = oldState == null ? null : oldState[i];
                 Object newValue = newState == null ? null : newState[i];
@@ -146,11 +145,14 @@
         if (verCfg.getEntCfg().isVersioned(entityName)) {
             AuditSync verSync = verCfg.getSyncManager().get(event.getSession());
 
-            verSync.addWorkUnit(new AddWorkUnit(event.getSession(), event.getPersister().getEntityName(), verCfg,
-					event.getId(), event.getPersister(), event.getState()));
+            AuditWorkUnit workUnit = new AddWorkUnit(event.getSession(), event.getPersister().getEntityName(), verCfg,
+                    event.getId(), event.getPersister(), event.getState());
+            verSync.addWorkUnit(workUnit);
 
-            generateBidirectionalCollectionChangeWorkUnits(verSync, event.getPersister(), entityName, event.getState(),
-                    null, event.getSession());
+            if (workUnit.containsWork()) {
+                generateBidirectionalCollectionChangeWorkUnits(verSync, event.getPersister(), entityName, event.getState(),
+                        null, event.getSession());
+            }
         }
     }
 
@@ -160,11 +162,14 @@
         if (verCfg.getEntCfg().isVersioned(entityName)) {
             AuditSync verSync = verCfg.getSyncManager().get(event.getSession());
 
-            verSync.addWorkUnit(new ModWorkUnit(event.getSession(), event.getPersister().getEntityName(), verCfg,
-					event.getId(), event.getPersister(), event.getState(), event.getOldState()));
+            AuditWorkUnit workUnit = new ModWorkUnit(event.getSession(), event.getPersister().getEntityName(), verCfg,
+                    event.getId(), event.getPersister(), event.getState(), event.getOldState());
+            verSync.addWorkUnit(workUnit);
 
-            generateBidirectionalCollectionChangeWorkUnits(verSync, event.getPersister(), entityName, event.getState(),
-                    event.getOldState(), event.getSession());
+            if (workUnit.containsWork()) {
+                generateBidirectionalCollectionChangeWorkUnits(verSync, event.getPersister(), entityName, event.getState(),
+                        event.getOldState(), event.getSession());
+            }
         }
     }
 
@@ -174,16 +179,20 @@
         if (verCfg.getEntCfg().isVersioned(entityName)) {
             AuditSync verSync = verCfg.getSyncManager().get(event.getSession());
 
-            verSync.addWorkUnit(new DelWorkUnit(event.getSession(), event.getPersister().getEntityName(), verCfg,
-					event.getId(), event.getPersister(), event.getDeletedState()));
+            AuditWorkUnit workUnit = new DelWorkUnit(event.getSession(), event.getPersister().getEntityName(), verCfg,
+                    event.getId(), event.getPersister(), event.getDeletedState());
+            verSync.addWorkUnit(workUnit);
 
-            generateBidirectionalCollectionChangeWorkUnits(verSync, event.getPersister(), entityName, null,
-                    event.getDeletedState(), event.getSession());
+            if (workUnit.containsWork()) {
+                generateBidirectionalCollectionChangeWorkUnits(verSync, event.getPersister(), entityName, null,
+                        event.getDeletedState(), event.getSession());
+            }
         }
     }
 
     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;
@@ -191,12 +200,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()) {
@@ -209,6 +215,39 @@
         }
     }
 
+    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, referencingPropertyName, event.getAffectedOwnerOrNull(), rd, revType,
+                    changeData.getChangedElementIndex(), 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();
@@ -216,16 +255,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/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/AuditSync.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/AuditSync.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/AuditSync.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -86,22 +86,16 @@
                 if (usedIds.containsKey(usedIdsKey)) {
                     AuditWorkUnit other = usedIds.get(usedIdsKey);
 
-                    // The entity with entityId has two work units; checking which one should be kept.
-                    switch (vwu.dispatch(other)) {
-                        case FIRST:
-                            // Simply not adding the second
-                            break;
+                    AuditWorkUnit result = vwu.dispatch(other);
 
-                        case SECOND:
-                            removeWorkUnit(other);
-                            usedIds.put(usedIdsKey, vwu);
-                            workUnits.offer(vwu);
-                            break;
+                    if (result != other) {
+                        removeWorkUnit(other);
 
-                        case NONE:
-                            removeWorkUnit(other);
-                            break;
-                    }
+                        if (result != null) {
+                            usedIds.put(usedIdsKey, result);
+                            workUnits.offer(result);
+                        } // else: a null result means that no work unit should be kept
+                    } // else: the result is the same as the work unit already added. No need to do anything.
                 } else {
                     usedIds.put(usedIdsKey, vwu);
                     workUnits.offer(vwu);

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/AbstractAuditWorkUnit.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/AbstractAuditWorkUnit.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/AbstractAuditWorkUnit.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -41,9 +41,8 @@
 	protected final SessionImplementor sessionImplementor;
     protected final AuditConfiguration verCfg;
     protected final Serializable id;
+    protected final String entityName;
 
-    private final String entityName;
-
     private Object performedData;
 
     protected AbstractAuditWorkUnit(SessionImplementor sessionImplementor, String entityName, AuditConfiguration verCfg,
@@ -65,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/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/AddWorkUnit.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/AddWorkUnit.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/AddWorkUnit.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -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;
 
@@ -38,50 +37,54 @@
  * @author Adam Warski (adam at warski dot org)
  */
 public class AddWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit {
-    private final Object[] state;
-    private final String[] propertyNames;
+    private final Map<String, Object> data;
 
     public AddWorkUnit(SessionImplementor sessionImplementor, String entityName, AuditConfiguration verCfg,
 					   Serializable id, EntityPersister entityPersister, Object[] state) {
         super(sessionImplementor, entityName, verCfg, id);
 
-        this.state = state;
-        this.propertyNames = entityPersister.getPropertyNames();
+        data = new HashMap<String, Object>();
+        verCfg.getEntCfg().get(getEntityName()).getPropertyMapper().map(sessionImplementor, data,
+				entityPersister.getPropertyNames(), state, null);
     }
 
+    public AddWorkUnit(SessionImplementor sessionImplementor, String entityName, AuditConfiguration verCfg,
+                       Serializable id, Map<String, Object> data) {
+        super(sessionImplementor, entityName, verCfg, id);
+
+        this.data = data;
+    }
+
     public boolean containsWork() {
         return true;
     }
 
-    public void perform(Session session, Object revisionData) {
-        Map<String, Object> data = new HashMap<String, Object>();
+    public Map<String, Object> generateData(Object revisionData) {
         fillDataWithId(data, revisionData, RevisionType.ADD);
+        return data;
+    }
 
-        verCfg.getEntCfg().get(getEntityName()).getPropertyMapper().map(sessionImplementor, data,
-				propertyNames, state, null);
-
-        session.save(verCfg.getAuditEntCfg().getAuditEntityName(getEntityName()), data);
-
-        setPerformed(data);
+    public AuditWorkUnit merge(AddWorkUnit second) {
+        return second;
     }
 
-    public KeepCheckResult check(AddWorkUnit second) {
-        return KeepCheckResult.FIRST;
+    public AuditWorkUnit merge(ModWorkUnit second) {
+        return new AddWorkUnit(sessionImplementor, entityName, verCfg, id, second.getData());
     }
 
-    public KeepCheckResult check(ModWorkUnit second) {
-        return KeepCheckResult.SECOND;
+    public AuditWorkUnit merge(DelWorkUnit second) {
+        return null;
     }
 
-    public KeepCheckResult check(DelWorkUnit second) {
-        return KeepCheckResult.NONE;
+    public AuditWorkUnit merge(CollectionChangeWorkUnit second) {
+        return this;
     }
 
-    public KeepCheckResult check(CollectionChangeWorkUnit second) {
-        return KeepCheckResult.FIRST;
+    public AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second) {
+        return FakeBidirectionalRelationWorkUnit.merge(second, this, second.getNestedWorkUnit());
     }
 
-    public KeepCheckResult dispatch(KeepCheckVisitor first) {
-        return first.check(this);
+    public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
+        return first.merge(this);
     }
 }

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/AuditWorkUnit.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/AuditWorkUnit.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/AuditWorkUnit.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -25,17 +25,33 @@
 
 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 KeepCheckVisitor, KeepCheckDispatcher {
+public interface AuditWorkUnit extends WorkUnitMergeVisitor, WorkUnitMergeDispatcher {
     Object getEntityId();
     String getEntityName();
     
     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/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/CollectionChangeWorkUnit.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/CollectionChangeWorkUnit.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/CollectionChangeWorkUnit.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -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,35 +49,37 @@
         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);
+        return data;
+    }
 
-        setPerformed(data);
+    public AuditWorkUnit merge(AddWorkUnit second) {
+        return second;
     }
 
-    public KeepCheckResult check(AddWorkUnit second) {
-        return KeepCheckResult.SECOND;
+    public AuditWorkUnit merge(ModWorkUnit second) {
+        return second;
     }
 
-    public KeepCheckResult check(ModWorkUnit second) {
-        return KeepCheckResult.SECOND;
+    public AuditWorkUnit merge(DelWorkUnit second) {
+        return second;
     }
 
-    public KeepCheckResult check(DelWorkUnit second) {
-        return KeepCheckResult.SECOND;
+    public AuditWorkUnit merge(CollectionChangeWorkUnit second) {
+        return this;
     }
 
-    public KeepCheckResult check(CollectionChangeWorkUnit second) {
-        return KeepCheckResult.FIRST;
+    public AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second) {
+        return second;
     }
 
-    public KeepCheckResult dispatch(KeepCheckVisitor first) {
-        return first.check(this);
+    public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
+        return first.merge(this);
     }
 }

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/DelWorkUnit.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/DelWorkUnit.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/DelWorkUnit.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -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,28 +61,30 @@
 					propertyNames, state, state);
 		}
 
-        session.save(verCfg.getAuditEntCfg().getAuditEntityName(getEntityName()), data);
+        return data;
+    }
 
-        setPerformed(data);
+    public AuditWorkUnit merge(AddWorkUnit second) {
+        return null;
     }
 
-    public KeepCheckResult check(AddWorkUnit second) {
-        return KeepCheckResult.NONE;
+    public AuditWorkUnit merge(ModWorkUnit second) {
+        return null;
     }
 
-    public KeepCheckResult check(ModWorkUnit second) {
-        return KeepCheckResult.NONE;
+    public AuditWorkUnit merge(DelWorkUnit second) {
+        return this;
     }
 
-    public KeepCheckResult check(DelWorkUnit second) {
-        return KeepCheckResult.FIRST;
+    public AuditWorkUnit merge(CollectionChangeWorkUnit second) {
+        return this;
     }
 
-    public KeepCheckResult check(CollectionChangeWorkUnit second) {
-        return KeepCheckResult.FIRST;
+    public AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second) {
+        return this;
     }
 
-    public KeepCheckResult dispatch(KeepCheckVisitor first) {
-        return first.check(this);
+    public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
+        return first.merge(this);
     }
 }
\ No newline at end of file

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

Deleted: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/KeepCheckDispatcher.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/KeepCheckDispatcher.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/KeepCheckDispatcher.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -1,37 +0,0 @@
-/*
- * 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.synchronization.work;
-
-/**
- * Visitor patter dispatcher.
- * @author Adam Warski (adam at warski dot org)
- */
-public interface KeepCheckDispatcher {
-    /**
-     * Shuold be invoked on the second work unit.
-     * @param first First work unit (that is, the one added earlier).
-     * @return Which work unit should be kept.
-     */
-    KeepCheckResult dispatch(KeepCheckVisitor first);
-}

Deleted: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/KeepCheckResult.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/KeepCheckResult.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/KeepCheckResult.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -1,35 +0,0 @@
-/*
- * 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.synchronization.work;
-
-/**
- * Possible outcomes of selecting which work unit to keep, in case there are two work units for the same entity
- * with the same id.
- * @author Adam Warski (adam at warski dot org)
- */
-public enum KeepCheckResult {
-    FIRST,
-    SECOND,
-    NONE
-}

Deleted: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/KeepCheckVisitor.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/KeepCheckVisitor.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/KeepCheckVisitor.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -1,35 +0,0 @@
-/*
- * 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.synchronization.work;
-
-/**
- * Visitor pattern visitor. All methods should be invoked on the first work unit.
- * @author Adam Warski (adam at warski dot org)
- */
-public interface KeepCheckVisitor {
-    KeepCheckResult check(AddWorkUnit second);
-    KeepCheckResult check(ModWorkUnit second);
-    KeepCheckResult check(DelWorkUnit second);
-    KeepCheckResult check(CollectionChangeWorkUnit second);
-}

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/ModWorkUnit.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/ModWorkUnit.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/ModWorkUnit.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -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,31 +53,37 @@
         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);
+        return data;
+    }
 
-        setPerformed(data);
+    public Map<String, Object> getData() {
+        return data;
     }
 
-    public KeepCheckResult check(AddWorkUnit second) {
-        return KeepCheckResult.FIRST;
+    public AuditWorkUnit merge(AddWorkUnit second) {
+        return this;
     }
 
-    public KeepCheckResult check(ModWorkUnit second) {
-        return KeepCheckResult.SECOND;
+    public AuditWorkUnit merge(ModWorkUnit second) {
+        return second;
     }
 
-    public KeepCheckResult check(DelWorkUnit second) {
-        return KeepCheckResult.SECOND;
+    public AuditWorkUnit merge(DelWorkUnit second) {
+        return second;
     }
 
-    public KeepCheckResult check(CollectionChangeWorkUnit second) {
-        return KeepCheckResult.FIRST;
+    public AuditWorkUnit merge(CollectionChangeWorkUnit second) {
+        return this;
     }
 
-    public KeepCheckResult dispatch(KeepCheckVisitor first) {
-        return first.check(this);
+    public AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second) {
+        return FakeBidirectionalRelationWorkUnit.merge(second, this, second.getNestedWorkUnit());
     }
+
+    public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
+        return first.merge(this);
+    }
 }
\ No newline at end of file

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/PersistentCollectionChangeWorkUnit.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/PersistentCollectionChangeWorkUnit.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/PersistentCollectionChangeWorkUnit.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -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,20 +47,34 @@
 
     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);
     }
 
+    public PersistentCollectionChangeWorkUnit(SessionImplementor sessionImplementor, String entityName,
+                                              AuditConfiguration verCfg, Serializable id,
+                                              List<PersistentCollectionChangeData> collectionChanges,
+                                              String referencingPropertyName) {
+        super(sessionImplementor, entityName, verCfg, id);
+
+        this.collectionChanges = collectionChanges;
+        this.referencingPropertyName = referencingPropertyName;
+    }
+
     public boolean containsWork() {
         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();
@@ -83,23 +96,27 @@
         return collectionChanges;
     }
 
-    public KeepCheckResult check(AddWorkUnit second) {
+    public AuditWorkUnit merge(AddWorkUnit second) {
         return null;
     }
 
-    public KeepCheckResult check(ModWorkUnit second) {
+    public AuditWorkUnit merge(ModWorkUnit second) {
         return null;
     }
 
-    public KeepCheckResult check(DelWorkUnit second) {
+    public AuditWorkUnit merge(DelWorkUnit second) {
         return null;
     }
 
-    public KeepCheckResult check(CollectionChangeWorkUnit second) {
+    public AuditWorkUnit merge(CollectionChangeWorkUnit second) {
         return null;
     }
 
-    public KeepCheckResult dispatch(KeepCheckVisitor first) {
+    public AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second) {
+        return null;
+    }
+
+    public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
         if (first instanceof PersistentCollectionChangeWorkUnit) {
             PersistentCollectionChangeWorkUnit original = (PersistentCollectionChangeWorkUnit) first;
 
@@ -115,26 +132,25 @@
                         persistentCollectionChangeData);
             }
 
-            // Storing the current changes
-            List<PersistentCollectionChangeData> newChanges = new ArrayList<PersistentCollectionChangeData>();
-            newChanges.addAll(collectionChanges);
+            // This will be the list with the resulting (merged) changes.
+            List<PersistentCollectionChangeData> mergedChanges = new ArrayList<PersistentCollectionChangeData>();
 
-            // And building the change list again
-            collectionChanges.clear();
+            // Including only those original changes, which are not overshadowed by new ones.
             for (PersistentCollectionChangeData originalCollectionChangeData : original.getCollectionChanges()) {
                 if (!newChangesIdMap.containsKey(getOriginalId(originalCollectionChangeData))) {
-                    collectionChanges.add(originalCollectionChangeData);
+                    mergedChanges.add(originalCollectionChangeData);
                 }
             }
 
             // Finally adding all of the new changes to the end of the list
-            collectionChanges.addAll(newChanges);
+            mergedChanges.addAll(getCollectionChanges());
+
+            return new PersistentCollectionChangeWorkUnit(sessionImplementor, entityName, verCfg, id, mergedChanges, 
+                    referencingPropertyName);
         } else {
             throw new RuntimeException("Trying to merge a " + first + " with a PersitentCollectionChangeWorkUnit. " +
                     "This is not really possible.");
         }
-
-        return KeepCheckResult.SECOND;
     }
 
     private Object getOriginalId(PersistentCollectionChangeData persistentCollectionChangeData) {

Copied: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/WorkUnitMergeDispatcher.java (from rev 18236, core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/WorkUnitMergeDispatcher.java)
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/WorkUnitMergeDispatcher.java	                        (rev 0)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/WorkUnitMergeDispatcher.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -0,0 +1,37 @@
+/*
+ * 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.synchronization.work;
+
+/**
+ * Visitor patter dispatcher.
+ * @author Adam Warski (adam at warski dot org)
+ */
+public interface WorkUnitMergeDispatcher {
+    /**
+     * Shuold be invoked on the second work unit.
+     * @param first First work unit (that is, the one added earlier).
+     * @return The work unit that is the result of the merge.
+     */
+    AuditWorkUnit dispatch(WorkUnitMergeVisitor first);
+}

Copied: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/WorkUnitMergeVisitor.java (from rev 18236, core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/WorkUnitMergeVisitor.java)
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/WorkUnitMergeVisitor.java	                        (rev 0)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/synchronization/work/WorkUnitMergeVisitor.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -0,0 +1,36 @@
+/*
+ * 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.synchronization.work;
+
+/**
+ * Visitor pattern visitor. All methods should be invoked on the first work unit.
+ * @author Adam Warski (adam at warski dot org)
+ */
+public interface WorkUnitMergeVisitor {
+    AuditWorkUnit merge(AddWorkUnit second);
+    AuditWorkUnit merge(ModWorkUnit second);
+    AuditWorkUnit merge(DelWorkUnit second);
+    AuditWorkUnit merge(CollectionChangeWorkUnit second);
+    AuditWorkUnit merge(FakeBidirectionalRelationWorkUnit second);
+}

Modified: core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/tools/MappingTools.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/tools/MappingTools.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/main/java/org/hibernate/envers/tools/MappingTools.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -1,5 +1,10 @@
 package org.hibernate.envers.tools;
 
+import org.hibernate.mapping.Collection;
+import org.hibernate.mapping.OneToMany;
+import org.hibernate.mapping.ToOne;
+import org.hibernate.mapping.Value;
+
 /**
  * @author Adam Warski (adam at warski dot org)
  */
@@ -12,4 +17,24 @@
 	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 + "_";
+    }
+
+    public static String getReferencedEntityName(Value value) {
+        if (value instanceof ToOne) {
+            return ((ToOne) value).getReferencedEntityName();
+        } else if (value instanceof OneToMany) {
+            return ((OneToMany) value).getReferencedEntityName();
+        } else if (value instanceof Collection) {
+            return getReferencedEntityName(((Collection) value).getElement());
+        }
+
+        return null;
+    }
 }

Copied: core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/DoubleListJoinColumnBidirectionalRefEdEntity1.java (from rev 18236, core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/DoubleListJoinColumnBidirectionalRefEdEntity1.java)
===================================================================
--- core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/DoubleListJoinColumnBidirectionalRefEdEntity1.java	                        (rev 0)
+++ core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/DoubleListJoinColumnBidirectionalRefEdEntity1.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -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.DoubleJoinColumnBidirectionalList} test.
+ * Owned side of the first relation.
+ * @author Adam Warski (adam at warski dot org)
+ */
+ at Entity
+ at Audited
+public class DoubleListJoinColumnBidirectionalRefEdEntity1 {
+    @Id
+    @GeneratedValue
+    private Integer id;
+
+    private String data;
+
+    @ManyToOne
+    @JoinColumn(name = "some_join_column_1", insertable = false, updatable = false)
+    private DoubleListJoinColumnBidirectionalRefIngEntity owner;
+
+    public DoubleListJoinColumnBidirectionalRefEdEntity1() { }
+
+    public DoubleListJoinColumnBidirectionalRefEdEntity1(Integer id, String data, DoubleListJoinColumnBidirectionalRefIngEntity owner) {
+        this.id = id;
+        this.data = data;
+        this.owner = owner;
+    }
+
+    public DoubleListJoinColumnBidirectionalRefEdEntity1(String data, DoubleListJoinColumnBidirectionalRefIngEntity 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 DoubleListJoinColumnBidirectionalRefIngEntity getOwner() {
+        return owner;
+    }
+
+    public void setOwner(DoubleListJoinColumnBidirectionalRefIngEntity owner) {
+        this.owner = owner;
+    }
+
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof DoubleListJoinColumnBidirectionalRefEdEntity1)) return false;
+
+        DoubleListJoinColumnBidirectionalRefEdEntity1 that = (DoubleListJoinColumnBidirectionalRefEdEntity1) 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 "DoubleListJoinColumnBidirectionalRefIngEntity1(id = " + id + ", data = " + data + ")";
+    }
+}
\ No newline at end of file

Copied: core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/DoubleListJoinColumnBidirectionalRefEdEntity2.java (from rev 18236, core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/DoubleListJoinColumnBidirectionalRefEdEntity2.java)
===================================================================
--- core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/DoubleListJoinColumnBidirectionalRefEdEntity2.java	                        (rev 0)
+++ core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/DoubleListJoinColumnBidirectionalRefEdEntity2.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -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.DoubleJoinColumnBidirectionalList} test.
+ * Owned side of the second relation.
+ * @author Adam Warski (adam at warski dot org)
+ */
+ at Entity
+ at Audited
+public class DoubleListJoinColumnBidirectionalRefEdEntity2 {
+    @Id
+    @GeneratedValue
+    private Integer id;
+
+    private String data;
+
+    @ManyToOne
+    @JoinColumn(name = "some_join_column_2", insertable = false, updatable = false)
+    private DoubleListJoinColumnBidirectionalRefIngEntity owner;
+
+    public DoubleListJoinColumnBidirectionalRefEdEntity2() { }
+
+    public DoubleListJoinColumnBidirectionalRefEdEntity2(Integer id, String data, DoubleListJoinColumnBidirectionalRefIngEntity owner) {
+        this.id = id;
+        this.data = data;
+        this.owner = owner;
+    }
+
+    public DoubleListJoinColumnBidirectionalRefEdEntity2(String data, DoubleListJoinColumnBidirectionalRefIngEntity 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 DoubleListJoinColumnBidirectionalRefIngEntity getOwner() {
+        return owner;
+    }
+
+    public void setOwner(DoubleListJoinColumnBidirectionalRefIngEntity owner) {
+        this.owner = owner;
+    }
+
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof DoubleListJoinColumnBidirectionalRefEdEntity2)) return false;
+
+        DoubleListJoinColumnBidirectionalRefEdEntity2 that = (DoubleListJoinColumnBidirectionalRefEdEntity2) 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 "DoubleListJoinColumnBidirectionalRefIngEntity2(id = " + id + ", data = " + data + ")";
+    }
+}
\ No newline at end of file

Copied: core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/DoubleListJoinColumnBidirectionalRefIngEntity.java (from rev 18236, core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/DoubleListJoinColumnBidirectionalRefIngEntity.java)
===================================================================
--- core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/DoubleListJoinColumnBidirectionalRefIngEntity.java	                        (rev 0)
+++ core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/DoubleListJoinColumnBidirectionalRefIngEntity.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -0,0 +1,100 @@
+package org.hibernate.envers.test.entities.onetomany.detached;
+
+import org.hibernate.envers.AuditMappedBy;
+import org.hibernate.envers.Audited;
+
+import javax.persistence.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Entity for {@link org.hibernate.envers.test.integration.onetomany.detached.DoubleJoinColumnBidirectionalList} test.
+ * Owning side of the relations.
+ * @author Adam Warski (adam at warski dot org)
+ */
+ at Entity
+ at Audited
+public class DoubleListJoinColumnBidirectionalRefIngEntity {
+    @Id
+    @GeneratedValue
+    private Integer id;
+
+    private String data;
+
+    @OneToMany
+    @JoinColumn(name = "some_join_column_1")
+    @AuditMappedBy(mappedBy = "owner")
+    private List<DoubleListJoinColumnBidirectionalRefEdEntity1> references1 = new ArrayList<DoubleListJoinColumnBidirectionalRefEdEntity1>();
+
+    @OneToMany
+    @JoinColumn(name = "some_join_column_2")
+    @AuditMappedBy(mappedBy = "owner")
+    private List<DoubleListJoinColumnBidirectionalRefEdEntity2> references2 = new ArrayList<DoubleListJoinColumnBidirectionalRefEdEntity2>();
+
+    public DoubleListJoinColumnBidirectionalRefIngEntity() { }
+
+    public DoubleListJoinColumnBidirectionalRefIngEntity(Integer id, String data) {
+        this.id = id;
+        this.data = data;
+    }
+
+    public DoubleListJoinColumnBidirectionalRefIngEntity(String data) {
+        this(null, data);
+    }
+
+    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<DoubleListJoinColumnBidirectionalRefEdEntity1> getReferences1() {
+        return references1;
+    }
+
+    public void setReferences1(List<DoubleListJoinColumnBidirectionalRefEdEntity1> references1) {
+        this.references1 = references1;
+    }
+
+    public List<DoubleListJoinColumnBidirectionalRefEdEntity2> getReferences2() {
+        return references2;
+    }
+
+    public void setReferences2(List<DoubleListJoinColumnBidirectionalRefEdEntity2> references2) {
+        this.references2 = references2;
+    }
+
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof DoubleListJoinColumnBidirectionalRefIngEntity)) return false;
+
+        DoubleListJoinColumnBidirectionalRefIngEntity that = (DoubleListJoinColumnBidirectionalRefIngEntity) 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 "DoubleListJoinColumnBidirectionalRefIngEntity(id = " + id + ", data = " + data + ")";
+    }
+}
\ No newline at end of file

Copied: core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefEdEntity.java (from rev 18236, core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefEdEntity.java)
===================================================================
--- core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefEdEntity.java	                        (rev 0)
+++ core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefEdEntity.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -0,0 +1,96 @@
+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.IndexedJoinColumnBidirectionalList} test.
+ * Owned side of the relation.
+ * @author Adam Warski (adam at warski dot org)
+ */
+ at Entity
+ at Audited
+public class IndexedListJoinColumnBidirectionalRefEdEntity {
+    @Id
+    @GeneratedValue
+    private Integer id;
+
+    private String data;
+
+    @Column(name = "indexed_index", insertable = false, updatable = false)
+    private Integer position;
+
+    @ManyToOne
+    @JoinColumn(name = "indexed_join_column", insertable = false, updatable = false)
+    private IndexedListJoinColumnBidirectionalRefIngEntity owner;
+
+    public IndexedListJoinColumnBidirectionalRefEdEntity() { }
+
+    public IndexedListJoinColumnBidirectionalRefEdEntity(Integer id, String data, IndexedListJoinColumnBidirectionalRefIngEntity owner) {
+        this.id = id;
+        this.data = data;
+        this.owner = owner;
+    }
+
+    public IndexedListJoinColumnBidirectionalRefEdEntity(String data, IndexedListJoinColumnBidirectionalRefIngEntity 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 IndexedListJoinColumnBidirectionalRefIngEntity getOwner() {
+        return owner;
+    }
+
+    public void setOwner(IndexedListJoinColumnBidirectionalRefIngEntity owner) {
+        this.owner = owner;
+    }
+
+    public Integer getPosition() {
+        return position;
+    }
+
+    public void setPosition(Integer position) {
+        this.position = position;
+    }
+
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof IndexedListJoinColumnBidirectionalRefEdEntity)) return false;
+
+        IndexedListJoinColumnBidirectionalRefEdEntity that = (IndexedListJoinColumnBidirectionalRefEdEntity) 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 "IndexedListJoinColumnBidirectionalRefEdEntity(id = " + id + ", data = " + data + ")";
+    }
+}
\ No newline at end of file

Copied: core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefIngEntity.java (from rev 18236, core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefIngEntity.java)
===================================================================
--- core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefIngEntity.java	                        (rev 0)
+++ core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefIngEntity.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -0,0 +1,92 @@
+package org.hibernate.envers.test.entities.onetomany.detached;
+
+import org.hibernate.envers.Audited;
+import org.hibernate.envers.AuditMappedBy;
+import org.hibernate.annotations.IndexColumn;
+
+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.IndexedJoinColumnBidirectionalList} test.
+ * Owning side of the relation.
+ * @author Adam Warski (adam at warski dot org)
+ */
+ at Entity
+ at Audited
+public class IndexedListJoinColumnBidirectionalRefIngEntity {
+    @Id
+    @GeneratedValue
+    private Integer id;
+
+    private String data;
+
+    @OneToMany
+    @JoinColumn(name = "indexed_join_column")
+    @IndexColumn(name = "indexed_index")
+    @AuditMappedBy(mappedBy = "owner", positionMappedBy = "position")
+    private List<IndexedListJoinColumnBidirectionalRefEdEntity> references;
+
+    public IndexedListJoinColumnBidirectionalRefIngEntity() { }
+
+    public IndexedListJoinColumnBidirectionalRefIngEntity(Integer id, String data, IndexedListJoinColumnBidirectionalRefEdEntity... references) {
+        this.id = id;
+        this.data = data;
+        this.references = new ArrayList<IndexedListJoinColumnBidirectionalRefEdEntity>();
+        this.references.addAll(Arrays.asList(references));
+    }
+
+    public IndexedListJoinColumnBidirectionalRefIngEntity(String data, IndexedListJoinColumnBidirectionalRefEdEntity... 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<IndexedListJoinColumnBidirectionalRefEdEntity> getReferences() {
+        return references;
+    }
+
+    public void setReferences(List<IndexedListJoinColumnBidirectionalRefEdEntity> references) {
+        this.references = references;
+    }
+
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof IndexedListJoinColumnBidirectionalRefIngEntity)) return false;
+
+        IndexedListJoinColumnBidirectionalRefIngEntity that = (IndexedListJoinColumnBidirectionalRefIngEntity) 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 "IndexedListJoinColumnBidirectionalRefIngEntity(id = " + id + ", data = " + data + ")";
+    }
+}
\ No newline at end of file

Copied: core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefEdEntity.java (from rev 18236, core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefEdEntity.java)
===================================================================
--- core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefEdEntity.java	                        (rev 0)
+++ core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefEdEntity.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -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)
+ */
+ at Entity
+ at 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 + ")";
+    }
+}

Copied: core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefIngEntity.java (from rev 18236, core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefIngEntity.java)
===================================================================
--- core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefIngEntity.java	                        (rev 0)
+++ core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefIngEntity.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -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)
+ */
+ at Entity
+ at 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 + ")";
+    }
+}

Modified: core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/flush/DoubleFlushAddMod.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/flush/DoubleFlushAddMod.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/flush/DoubleFlushAddMod.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -24,11 +24,15 @@
 package org.hibernate.envers.test.integration.flush;
 
 import java.util.Arrays;
+import java.util.List;
 import javax.persistence.EntityManager;
 
 import org.hibernate.envers.test.entities.StrTestEntity;
+import org.hibernate.envers.query.AuditEntity;
+import org.hibernate.envers.RevisionType;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
+import static org.testng.Assert.*;
 
 import org.hibernate.FlushMode;
 
@@ -87,4 +91,16 @@
         assert getAuditReader().find(StrTestEntity.class, id, 1).equals(ver1);
         assert getAuditReader().find(StrTestEntity.class, id, 2).equals(ver2);
     }
+
+    @Test
+    public void testRevisionTypes() {
+        @SuppressWarnings({"unchecked"}) List<Object[]> results =
+                getAuditReader().createQuery()
+                        .forRevisionsOfEntity(StrTestEntity.class, false, true)
+                        .add(AuditEntity.id().eq(id))
+                        .getResultList();
+        
+        assertEquals(results.get(0)[2], RevisionType.ADD);
+        assertEquals(results.get(1)[2], RevisionType.MOD);
+    }
 }
\ No newline at end of file

Modified: core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/flush/DoubleFlushModDel.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/flush/DoubleFlushModDel.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/flush/DoubleFlushModDel.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -24,11 +24,15 @@
 package org.hibernate.envers.test.integration.flush;
 
 import java.util.Arrays;
+import java.util.List;
 import javax.persistence.EntityManager;
 
 import org.hibernate.envers.test.entities.StrTestEntity;
+import org.hibernate.envers.query.AuditEntity;
+import org.hibernate.envers.RevisionType;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
 
 import org.hibernate.FlushMode;
 
@@ -83,4 +87,16 @@
         assert getAuditReader().find(StrTestEntity.class, id, 1).equals(ver1);
         assert getAuditReader().find(StrTestEntity.class, id, 2) == null;
     }
+
+    @Test
+    public void testRevisionTypes() {
+        @SuppressWarnings({"unchecked"}) List<Object[]> results =
+                getAuditReader().createQuery()
+                        .forRevisionsOfEntity(StrTestEntity.class, false, true)
+                        .add(AuditEntity.id().eq(id))
+                        .getResultList();
+
+        assertEquals(results.get(0)[2], RevisionType.ADD);
+        assertEquals(results.get(1)[2], RevisionType.DEL);
+    }
 }
\ No newline at end of file

Modified: core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/flush/DoubleFlushModMod.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/flush/DoubleFlushModMod.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/flush/DoubleFlushModMod.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -24,11 +24,15 @@
 package org.hibernate.envers.test.integration.flush;
 
 import java.util.Arrays;
+import java.util.List;
 import javax.persistence.EntityManager;
 
 import org.hibernate.envers.test.entities.StrTestEntity;
+import org.hibernate.envers.query.AuditEntity;
+import org.hibernate.envers.RevisionType;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
 
 import org.hibernate.FlushMode;
 
@@ -84,4 +88,16 @@
         assert getAuditReader().find(StrTestEntity.class, id, 1).equals(ver1);
         assert getAuditReader().find(StrTestEntity.class, id, 2).equals(ver2);
     }
+
+    @Test
+    public void testRevisionTypes() {
+        @SuppressWarnings({"unchecked"}) List<Object[]> results =
+                getAuditReader().createQuery()
+                        .forRevisionsOfEntity(StrTestEntity.class, false, true)
+                        .add(AuditEntity.id().eq(id))
+                        .getResultList();
+
+        assertEquals(results.get(0)[2], RevisionType.ADD);
+        assertEquals(results.get(1)[2], RevisionType.MOD);
+    }
 }
\ No newline at end of file

Modified: core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/flush/ManualFlush.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/flush/ManualFlush.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/flush/ManualFlush.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -24,11 +24,15 @@
 package org.hibernate.envers.test.integration.flush;
 
 import java.util.Arrays;
+import java.util.List;
 import javax.persistence.EntityManager;
 
 import org.hibernate.envers.test.entities.StrTestEntity;
+import org.hibernate.envers.query.AuditEntity;
+import org.hibernate.envers.RevisionType;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
 
 import org.hibernate.FlushMode;
 
@@ -96,4 +100,16 @@
     public void testCurrent() {
         assert getEntityManager().find(StrTestEntity.class, id).equals(new StrTestEntity("z", id));
     }
+
+    @Test
+    public void testRevisionTypes() {
+        @SuppressWarnings({"unchecked"}) List<Object[]> results =
+                getAuditReader().createQuery()
+                        .forRevisionsOfEntity(StrTestEntity.class, false, true)
+                        .add(AuditEntity.id().eq(id))
+                        .getResultList();
+
+        assertEquals(results.get(0)[2], RevisionType.ADD);
+        assertEquals(results.get(1)[2], RevisionType.MOD);
+    }
 }

Modified: core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/manytomany/unidirectional/M2MRelationNotAuditedTarget.java
===================================================================
--- core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/manytomany/unidirectional/M2MRelationNotAuditedTarget.java	2009-12-16 11:09:34 UTC (rev 18236)
+++ core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/manytomany/unidirectional/M2MRelationNotAuditedTarget.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -37,6 +37,7 @@
 
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
+import static org.testng.Assert.*;
 
 /**
  * A test for auditing a many-to-many relation where the target entity is not audited.
@@ -143,10 +144,10 @@
 		M2MTargetNotAuditedEntity rev3 = getAuditReader().find(M2MTargetNotAuditedEntity.class, tnae1_id, 3);
 		M2MTargetNotAuditedEntity rev4 = getAuditReader().find(M2MTargetNotAuditedEntity.class, tnae1_id, 4);
 
-		checkList(rev1.getReferences());
-		checkList(rev2.getReferences(), uste1);
-		checkList(rev3.getReferences(), uste1);
-		checkList(rev4.getReferences(), uste1, uste2);
+		assertTrue(checkList(rev1.getReferences()));
+		assertTrue(checkList(rev2.getReferences(), uste1));
+		assertTrue(checkList(rev3.getReferences(), uste1));
+		assertTrue(checkList(rev4.getReferences(), uste1, uste2));
 	}
 
 	@Test
@@ -159,9 +160,9 @@
 		M2MTargetNotAuditedEntity rev3 = getAuditReader().find(M2MTargetNotAuditedEntity.class, tnae2_id, 3);
 		M2MTargetNotAuditedEntity rev4 = getAuditReader().find(M2MTargetNotAuditedEntity.class, tnae2_id, 4);
 
-		checkList(rev1.getReferences(), uste1, uste2);
-		checkList(rev2.getReferences(), uste2);
-		checkList(rev3.getReferences());
-		checkList(rev4.getReferences(), uste1);
+		assertTrue(checkList(rev1.getReferences(), uste1, uste2));
+		assertTrue(checkList(rev2.getReferences(), uste2));
+		assertTrue(checkList(rev3.getReferences()));
+		assertTrue(checkList(rev4.getReferences(), uste1));
 	}
 }

Copied: core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/DoubleJoinColumnBidirectionalList.java (from rev 18236, core/trunk/envers/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/DoubleJoinColumnBidirectionalList.java)
===================================================================
--- core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/DoubleJoinColumnBidirectionalList.java	                        (rev 0)
+++ core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/DoubleJoinColumnBidirectionalList.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -0,0 +1,299 @@
+/*
+ * 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.DoubleListJoinColumnBidirectionalRefEdEntity1;
+import org.hibernate.envers.test.entities.onetomany.detached.DoubleListJoinColumnBidirectionalRefEdEntity2;
+import org.hibernate.envers.test.entities.onetomany.detached.DoubleListJoinColumnBidirectionalRefIngEntity;
+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 double "fake" bidirectional mapping where one side uses @OneToMany+ at JoinColumn
+ * (and thus owns the relation), and the other uses a @ManyToOne(insertable=false, updatable=false).
+ * @author Adam Warski (adam at warski dot org)
+ */
+public class DoubleJoinColumnBidirectionalList extends AbstractEntityTest {
+    private Integer ed1_1_id;
+    private Integer ed2_1_id;
+    private Integer ed1_2_id;
+    private Integer ed2_2_id;
+
+    private Integer ing1_id;
+    private Integer ing2_id;
+
+    public void configure(Ejb3Configuration cfg) {
+        cfg.addAnnotatedClass(DoubleListJoinColumnBidirectionalRefIngEntity.class);
+        cfg.addAnnotatedClass(DoubleListJoinColumnBidirectionalRefEdEntity1.class);
+        cfg.addAnnotatedClass(DoubleListJoinColumnBidirectionalRefEdEntity2.class);
+    }
+
+    @Test(enabled = true)
+    public void createData() {
+        EntityManager em = getEntityManager();
+
+        DoubleListJoinColumnBidirectionalRefEdEntity1 ed1_1 = new DoubleListJoinColumnBidirectionalRefEdEntity1("ed1_1", null);
+        DoubleListJoinColumnBidirectionalRefEdEntity1 ed1_2 = new DoubleListJoinColumnBidirectionalRefEdEntity1("ed1_2", null);
+
+        DoubleListJoinColumnBidirectionalRefEdEntity2 ed2_1 = new DoubleListJoinColumnBidirectionalRefEdEntity2("ed2_1", null);
+        DoubleListJoinColumnBidirectionalRefEdEntity2 ed2_2 = new DoubleListJoinColumnBidirectionalRefEdEntity2("ed2_2", null);
+
+        DoubleListJoinColumnBidirectionalRefIngEntity ing1 = new DoubleListJoinColumnBidirectionalRefIngEntity("coll1");
+        DoubleListJoinColumnBidirectionalRefIngEntity ing2 = new DoubleListJoinColumnBidirectionalRefIngEntity("coll2");
+
+        // Revision 1 (ing1: ed1_1, ed2_1, ing2: ed1_2, ed2_2)
+        em.getTransaction().begin();
+
+        ing1.getReferences1().add(ed1_1);
+        ing1.getReferences2().add(ed2_1);
+
+        ing2.getReferences1().add(ed1_2);
+        ing2.getReferences2().add(ed2_2);
+
+        em.persist(ed1_1);
+        em.persist(ed1_2);
+        em.persist(ed2_1);
+        em.persist(ed2_2);
+        em.persist(ing1);
+        em.persist(ing2);
+
+        em.getTransaction().commit();
+
+        // Revision 2 (ing1: ed1_1, ed1_2, ed2_1, ed2_2)
+        em.getTransaction().begin();
+
+        ing1 = em.find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing1.getId());
+        ing2 = em.find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing2.getId());
+        ed1_1 = em.find(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_1.getId());
+        ed1_2 = em.find(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_2.getId());
+        ed2_1 = em.find(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_1.getId());
+        ed2_2 = em.find(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_2.getId());
+
+        ing2.getReferences1().clear();
+        ing2.getReferences2().clear();
+
+        ing1.getReferences1().add(ed1_2);
+        ing1.getReferences2().add(ed2_2);
+
+        em.getTransaction().commit();
+        em.clear();
+
+        // Revision 3 (ing1: ed1_1, ed1_2, ed2_1, ed2_2)
+        em.getTransaction().begin();
+
+        ing1 = em.find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing1.getId());
+        ing2 = em.find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing2.getId());
+        ed1_1 = em.find(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_1.getId());
+        ed1_2 = em.find(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_2.getId());
+        ed2_1 = em.find(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_1.getId());
+        ed2_2 = em.find(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_2.getId());
+
+        ed1_1.setData("ed1_1 bis");
+        ed2_2.setData("ed2_2 bis");
+
+        em.getTransaction().commit();
+        em.clear();
+
+        // Revision 4 (ing1: ed2_2, ing2: ed2_1, ed1_1, ed1_2)
+        em.getTransaction().begin();
+
+        ing1 = em.find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing1.getId());
+        ing2 = em.find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing2.getId());
+        ed1_1 = em.find(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_1.getId());
+        ed1_2 = em.find(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_2.getId());
+        ed2_1 = em.find(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_1.getId());
+        ed2_2 = em.find(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_2.getId());
+
+        ing1.getReferences1().clear();
+        ing2.getReferences1().add(ed1_1);
+        ing2.getReferences1().add(ed1_2);
+
+        ing1.getReferences2().remove(ed2_1);
+        ing2.getReferences2().add(ed2_1);
+
+        em.getTransaction().commit();
+        em.clear();
+
+        //
+
+        ing1_id = ing1.getId();
+        ing2_id = ing2.getId();
+
+        ed1_1_id = ed1_1.getId();
+        ed1_2_id = ed1_2.getId();
+        ed2_1_id = ed2_1.getId();
+        ed2_2_id = ed2_2.getId();
+    }
+
+    @Test(enabled = true, dependsOnMethods = "createData")
+    public void testRevisionsCounts() {
+        assertEquals(Arrays.asList(1, 2, 4), getAuditReader().getRevisions(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing1_id));
+        assertEquals(Arrays.asList(1, 2, 4), getAuditReader().getRevisions(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing2_id));
+
+        assertEquals(Arrays.asList(1, 3, 4), getAuditReader().getRevisions(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_1_id));
+        assertEquals(Arrays.asList(1, 2, 4), getAuditReader().getRevisions(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_2_id));
+
+        assertEquals(Arrays.asList(1, 4), getAuditReader().getRevisions(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_1_id));
+        assertEquals(Arrays.asList(1, 2, 3), getAuditReader().getRevisions(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_2_id));
+    }
+
+    @Test(enabled = true, dependsOnMethods = "createData")
+    public void testHistoryOfIng1() {
+        DoubleListJoinColumnBidirectionalRefEdEntity1 ed1_1_fromRev1 = new DoubleListJoinColumnBidirectionalRefEdEntity1(ed1_1_id, "ed1_1", null);
+        DoubleListJoinColumnBidirectionalRefEdEntity1 ed1_1_fromRev3 = new DoubleListJoinColumnBidirectionalRefEdEntity1(ed1_1_id, "ed1_1 bis", null);
+        DoubleListJoinColumnBidirectionalRefEdEntity1 ed1_2 = getEntityManager().find(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_2_id);
+        DoubleListJoinColumnBidirectionalRefEdEntity2 ed2_1 = getEntityManager().find(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_1_id);
+        DoubleListJoinColumnBidirectionalRefEdEntity2 ed2_2_fromRev1 = new DoubleListJoinColumnBidirectionalRefEdEntity2(ed2_2_id, "ed2_2", null);
+        DoubleListJoinColumnBidirectionalRefEdEntity2 ed2_2_fromRev3 = new DoubleListJoinColumnBidirectionalRefEdEntity2(ed2_2_id, "ed2_2 bis", null);
+
+        DoubleListJoinColumnBidirectionalRefIngEntity rev1 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing1_id, 1);
+        DoubleListJoinColumnBidirectionalRefIngEntity rev2 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing1_id, 2);
+        DoubleListJoinColumnBidirectionalRefIngEntity rev3 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing1_id, 3);
+        DoubleListJoinColumnBidirectionalRefIngEntity rev4 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing1_id, 4);
+
+        assertTrue(checkList(rev1.getReferences1(), ed1_1_fromRev1));
+        assertTrue(checkList(rev2.getReferences1(), ed1_1_fromRev1, ed1_2));
+        assertTrue(checkList(rev3.getReferences1(), ed1_1_fromRev3, ed1_2));
+        assertTrue(checkList(rev4.getReferences1()));
+
+        assertTrue(checkList(rev1.getReferences2(), ed2_1));
+        assertTrue(checkList(rev2.getReferences2(), ed2_1, ed2_2_fromRev1));
+        assertTrue(checkList(rev3.getReferences2(), ed2_1, ed2_2_fromRev3));
+        assertTrue(checkList(rev4.getReferences2(), ed2_2_fromRev3));
+    }
+
+    @Test(enabled = true, dependsOnMethods = "createData")
+    public void testHistoryOfIng2() {
+        DoubleListJoinColumnBidirectionalRefEdEntity1 ed1_1_fromRev3 = new DoubleListJoinColumnBidirectionalRefEdEntity1(ed1_1_id, "ed1_1 bis", null);
+        DoubleListJoinColumnBidirectionalRefEdEntity1 ed1_2 = getEntityManager().find(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_2_id);
+        DoubleListJoinColumnBidirectionalRefEdEntity2 ed2_1 = getEntityManager().find(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_1_id);
+        DoubleListJoinColumnBidirectionalRefEdEntity2 ed2_2_fromRev1 = new DoubleListJoinColumnBidirectionalRefEdEntity2(ed2_2_id, "ed2_2", null);
+
+        DoubleListJoinColumnBidirectionalRefIngEntity rev1 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing2_id, 1);
+        DoubleListJoinColumnBidirectionalRefIngEntity rev2 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing2_id, 2);
+        DoubleListJoinColumnBidirectionalRefIngEntity rev3 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing2_id, 3);
+        DoubleListJoinColumnBidirectionalRefIngEntity rev4 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing2_id, 4);
+
+        assertTrue(checkList(rev1.getReferences1(), ed1_2));
+        assertTrue(checkList(rev2.getReferences1()));
+        assertTrue(checkList(rev3.getReferences1()));
+        assertTrue(checkList(rev4.getReferences1(), ed1_1_fromRev3, ed1_2));
+
+        assertTrue(checkList(rev1.getReferences2(), ed2_2_fromRev1));
+        assertTrue(checkList(rev2.getReferences2()));
+        assertTrue(checkList(rev3.getReferences2()));
+        assertTrue(checkList(rev4.getReferences2(), ed2_1));
+    }
+
+    @Test(enabled = true, dependsOnMethods = "createData")
+    public void testHistoryOfEd1_1() {
+        DoubleListJoinColumnBidirectionalRefIngEntity ing1 = getEntityManager().find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing1_id);
+        DoubleListJoinColumnBidirectionalRefIngEntity ing2 = getEntityManager().find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing2_id);
+
+        DoubleListJoinColumnBidirectionalRefEdEntity1 rev1 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_1_id, 1);
+        DoubleListJoinColumnBidirectionalRefEdEntity1 rev2 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_1_id, 2);
+        DoubleListJoinColumnBidirectionalRefEdEntity1 rev3 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_1_id, 3);
+        DoubleListJoinColumnBidirectionalRefEdEntity1 rev4 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_1_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_1");
+        assertEquals(rev2.getData(), "ed1_1");
+        assertEquals(rev3.getData(), "ed1_1 bis");
+        assertEquals(rev4.getData(), "ed1_1 bis");
+    }
+
+    @Test(enabled = true, dependsOnMethods = "createData")
+    public void testHistoryOfEd1_2() {
+        DoubleListJoinColumnBidirectionalRefIngEntity ing1 = getEntityManager().find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing1_id);
+        DoubleListJoinColumnBidirectionalRefIngEntity ing2 = getEntityManager().find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing2_id);
+
+        DoubleListJoinColumnBidirectionalRefEdEntity1 rev1 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_2_id, 1);
+        DoubleListJoinColumnBidirectionalRefEdEntity1 rev2 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_2_id, 2);
+        DoubleListJoinColumnBidirectionalRefEdEntity1 rev3 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_2_id, 3);
+        DoubleListJoinColumnBidirectionalRefEdEntity1 rev4 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefEdEntity1.class, ed1_2_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(), "ed1_2");
+        assertEquals(rev2.getData(), "ed1_2");
+        assertEquals(rev3.getData(), "ed1_2");
+        assertEquals(rev4.getData(), "ed1_2");
+    }
+
+    @Test(enabled = true, dependsOnMethods = "createData")
+    public void testHistoryOfEd2_1() {
+        DoubleListJoinColumnBidirectionalRefIngEntity ing1 = getEntityManager().find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing1_id);
+        DoubleListJoinColumnBidirectionalRefIngEntity ing2 = getEntityManager().find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing2_id);
+
+        DoubleListJoinColumnBidirectionalRefEdEntity2 rev1 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_1_id, 1);
+        DoubleListJoinColumnBidirectionalRefEdEntity2 rev2 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_1_id, 2);
+        DoubleListJoinColumnBidirectionalRefEdEntity2 rev3 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_1_id, 3);
+        DoubleListJoinColumnBidirectionalRefEdEntity2 rev4 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_1_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(), "ed2_1");
+        assertEquals(rev2.getData(), "ed2_1");
+        assertEquals(rev3.getData(), "ed2_1");
+        assertEquals(rev4.getData(), "ed2_1");
+    }
+
+    @Test(enabled = true, dependsOnMethods = "createData")
+    public void testHistoryOfEd2_2() {
+        DoubleListJoinColumnBidirectionalRefIngEntity ing1 = getEntityManager().find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing1_id);
+        DoubleListJoinColumnBidirectionalRefIngEntity ing2 = getEntityManager().find(DoubleListJoinColumnBidirectionalRefIngEntity.class, ing2_id);
+
+        DoubleListJoinColumnBidirectionalRefEdEntity2 rev1 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_2_id, 1);
+        DoubleListJoinColumnBidirectionalRefEdEntity2 rev2 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_2_id, 2);
+        DoubleListJoinColumnBidirectionalRefEdEntity2 rev3 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_2_id, 3);
+        DoubleListJoinColumnBidirectionalRefEdEntity2 rev4 = getAuditReader().find(DoubleListJoinColumnBidirectionalRefEdEntity2.class, ed2_2_id, 4);
+
+        assertTrue(rev1.getOwner().equals(ing2));
+        assertTrue(rev2.getOwner().equals(ing1));
+        assertTrue(rev3.getOwner().equals(ing1));
+        assertTrue(rev4.getOwner().equals(ing1));
+
+        assertEquals(rev1.getData(), "ed2_2");
+        assertEquals(rev2.getData(), "ed2_2");
+        assertEquals(rev3.getData(), "ed2_2 bis");
+        assertEquals(rev4.getData(), "ed2_2 bis");
+    }
+}
\ No newline at end of file

Copied: core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/IndexedJoinColumnBidirectionalList.java (from rev 18236, core/trunk/envers/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/IndexedJoinColumnBidirectionalList.java)
===================================================================
--- core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/IndexedJoinColumnBidirectionalList.java	                        (rev 0)
+++ core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/IndexedJoinColumnBidirectionalList.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -0,0 +1,250 @@
+/*
+ * 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.IndexedListJoinColumnBidirectionalRefIngEntity;
+import org.hibernate.envers.test.entities.onetomany.detached.IndexedListJoinColumnBidirectionalRefEdEntity;
+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+ at 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 IndexedJoinColumnBidirectionalList extends AbstractEntityTest {
+    private Integer ed1_id;
+    private Integer ed2_id;
+    private Integer ed3_id;
+
+    private Integer ing1_id;
+    private Integer ing2_id;
+
+    public void configure(Ejb3Configuration cfg) {
+        cfg.addAnnotatedClass(IndexedListJoinColumnBidirectionalRefIngEntity.class);
+        cfg.addAnnotatedClass(IndexedListJoinColumnBidirectionalRefEdEntity.class);
+    }
+
+    @Test(enabled = true)
+    public void createData() {
+        EntityManager em = getEntityManager();
+
+        IndexedListJoinColumnBidirectionalRefEdEntity ed1 = new IndexedListJoinColumnBidirectionalRefEdEntity("ed1", null);
+        IndexedListJoinColumnBidirectionalRefEdEntity ed2 = new IndexedListJoinColumnBidirectionalRefEdEntity("ed2", null);
+        IndexedListJoinColumnBidirectionalRefEdEntity ed3 = new IndexedListJoinColumnBidirectionalRefEdEntity("ed3", null);
+
+        IndexedListJoinColumnBidirectionalRefIngEntity ing1 = new IndexedListJoinColumnBidirectionalRefIngEntity("coll1", ed1, ed2, ed3);
+        IndexedListJoinColumnBidirectionalRefIngEntity ing2 = new IndexedListJoinColumnBidirectionalRefIngEntity("coll1");
+
+        // Revision 1 (ing1: ed1, ed2, ed3)
+        em.getTransaction().begin();
+
+        em.persist(ed1);
+        em.persist(ed2);
+        em.persist(ed3);
+        em.persist(ing1);
+        em.persist(ing2);
+
+        em.getTransaction().commit();
+
+        // Revision 2 (ing1: ed1, ed3, ing2: ed2)
+        em.getTransaction().begin();
+
+        ing1 = em.find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing1.getId());
+        ing2 = em.find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing2.getId());
+        ed2 = em.find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed2.getId());
+
+        ing1.getReferences().remove(ed2);
+        ing2.getReferences().add(ed2);
+
+        em.getTransaction().commit();
+        em.clear();
+
+        // Revision 3 (ing1: ed3, ed1, ing2: ed2)
+        em.getTransaction().begin();
+
+        ing1 = em.find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing1.getId());
+        ing2 = em.find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing2.getId());
+        ed1 = em.find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed1.getId());
+        ed2 = em.find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed2.getId());
+        ed3 = em.find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed3.getId());
+
+        ing1.getReferences().remove(ed3);
+        ing1.getReferences().add(0, ed3);
+
+        em.getTransaction().commit();
+        em.clear();
+
+        // Revision 4 (ing1: ed2, ed3, ed1)
+        em.getTransaction().begin();
+
+        ing1 = em.find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing1.getId());
+        ing2 = em.find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing2.getId());
+        ed1 = em.find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed1.getId());
+        ed2 = em.find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed2.getId());
+        ed3 = em.find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed3.getId());
+
+        ing2.getReferences().remove(ed2);
+        ing1.getReferences().add(0, ed2);
+
+        em.getTransaction().commit();
+        em.clear();
+
+        //
+
+        ing1_id = ing1.getId();
+        ing2_id = ing2.getId();
+
+        ed1_id = ed1.getId();
+        ed2_id = ed2.getId();
+        ed3_id = ed3.getId();
+    }
+
+    @Test(enabled = true, dependsOnMethods = "createData")
+    public void testRevisionsCounts() {
+        assertEquals(Arrays.asList(1, 2, 3, 4), getAuditReader().getRevisions(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing1_id));
+        assertEquals(Arrays.asList(1, 2, 4), getAuditReader().getRevisions(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing2_id));
+
+        assertEquals(Arrays.asList(1, 3, 4), getAuditReader().getRevisions(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed1_id));
+        assertEquals(Arrays.asList(1, 2, 4), getAuditReader().getRevisions(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed2_id));
+        assertEquals(Arrays.asList(1, 2, 3, 4), getAuditReader().getRevisions(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed3_id));
+    }
+
+    @Test(enabled = true, dependsOnMethods = "createData")
+    public void testHistoryOfIng1() {
+        IndexedListJoinColumnBidirectionalRefEdEntity ed1 = getEntityManager().find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed1_id);
+        IndexedListJoinColumnBidirectionalRefEdEntity ed2 = getEntityManager().find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed2_id);
+        IndexedListJoinColumnBidirectionalRefEdEntity ed3 = getEntityManager().find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed3_id);
+
+        IndexedListJoinColumnBidirectionalRefIngEntity rev1 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing1_id, 1);
+        IndexedListJoinColumnBidirectionalRefIngEntity rev2 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing1_id, 2);
+        IndexedListJoinColumnBidirectionalRefIngEntity rev3 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing1_id, 3);
+        IndexedListJoinColumnBidirectionalRefIngEntity rev4 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing1_id, 4);
+
+        assertEquals(rev1.getReferences().size(), 3);
+        assertEquals(rev1.getReferences().get(0), ed1);
+        assertEquals(rev1.getReferences().get(1), ed2);
+        assertEquals(rev1.getReferences().get(2), ed3);
+
+        assertEquals(rev2.getReferences().size(), 2);
+        assertEquals(rev2.getReferences().get(0), ed1);
+        assertEquals(rev2.getReferences().get(1), ed3);
+
+        assertEquals(rev3.getReferences().size(), 2);
+        assertEquals(rev3.getReferences().get(0), ed3);
+        assertEquals(rev3.getReferences().get(1), ed1);
+
+        assertEquals(rev4.getReferences().size(), 3);
+        assertEquals(rev4.getReferences().get(0), ed2);
+        assertEquals(rev4.getReferences().get(1), ed3);
+        assertEquals(rev4.getReferences().get(2), ed1);
+    }
+
+    @Test(enabled = true, dependsOnMethods = "createData")
+    public void testHistoryOfIng2() {
+        IndexedListJoinColumnBidirectionalRefEdEntity ed2 = getEntityManager().find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed2_id);
+
+        IndexedListJoinColumnBidirectionalRefIngEntity rev1 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing2_id, 1);
+        IndexedListJoinColumnBidirectionalRefIngEntity rev2 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing2_id, 2);
+        IndexedListJoinColumnBidirectionalRefIngEntity rev3 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing2_id, 3);
+        IndexedListJoinColumnBidirectionalRefIngEntity rev4 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing2_id, 4);
+
+        assertEquals(rev1.getReferences().size(), 0);
+
+        assertEquals(rev2.getReferences().size(), 1);
+        assertEquals(rev2.getReferences().get(0), ed2);
+
+        assertEquals(rev3.getReferences().size(), 1);
+        assertEquals(rev3.getReferences().get(0), ed2);
+
+        assertEquals(rev4.getReferences().size(), 0);
+    }
+
+    @Test(enabled = true, dependsOnMethods = "createData")
+    public void testHistoryOfEd1() {
+        IndexedListJoinColumnBidirectionalRefIngEntity ing1 = getEntityManager().find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing1_id);
+
+        IndexedListJoinColumnBidirectionalRefEdEntity rev1 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed1_id, 1);
+        IndexedListJoinColumnBidirectionalRefEdEntity rev2 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed1_id, 2);
+        IndexedListJoinColumnBidirectionalRefEdEntity rev3 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed1_id, 3);
+        IndexedListJoinColumnBidirectionalRefEdEntity rev4 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed1_id, 4);
+
+        assertTrue(rev1.getOwner().equals(ing1));
+        assertTrue(rev2.getOwner().equals(ing1));
+        assertTrue(rev3.getOwner().equals(ing1));
+        assertTrue(rev4.getOwner().equals(ing1));
+
+        assertEquals(rev1.getPosition(), new Integer(0));
+        assertEquals(rev2.getPosition(), new Integer(0));
+        assertEquals(rev3.getPosition(), new Integer(1));
+        assertEquals(rev4.getPosition(), new Integer(2));
+    }
+
+    @Test(enabled = true, dependsOnMethods = "createData")
+    public void testHistoryOfEd2() {
+        IndexedListJoinColumnBidirectionalRefIngEntity ing1 = getEntityManager().find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing1_id);
+        IndexedListJoinColumnBidirectionalRefIngEntity ing2 = getEntityManager().find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing2_id);
+
+        IndexedListJoinColumnBidirectionalRefEdEntity rev1 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed2_id, 1);
+        IndexedListJoinColumnBidirectionalRefEdEntity rev2 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed2_id, 2);
+        IndexedListJoinColumnBidirectionalRefEdEntity rev3 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed2_id, 3);
+        IndexedListJoinColumnBidirectionalRefEdEntity rev4 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed2_id, 4);
+
+        assertTrue(rev1.getOwner().equals(ing1));
+        assertTrue(rev2.getOwner().equals(ing2));
+        assertTrue(rev3.getOwner().equals(ing2));
+        assertTrue(rev4.getOwner().equals(ing1));
+
+        assertEquals(rev1.getPosition(), new Integer(1));
+        assertEquals(rev2.getPosition(), new Integer(0));
+        assertEquals(rev3.getPosition(), new Integer(0));
+        assertEquals(rev4.getPosition(), new Integer(0));
+    }
+
+    @Test(enabled = true, dependsOnMethods = "createData")
+    public void testHistoryOfEd3() {
+        IndexedListJoinColumnBidirectionalRefIngEntity ing1 = getEntityManager().find(IndexedListJoinColumnBidirectionalRefIngEntity.class, ing1_id);
+
+        IndexedListJoinColumnBidirectionalRefEdEntity rev1 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed3_id, 1);
+        IndexedListJoinColumnBidirectionalRefEdEntity rev2 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed3_id, 2);
+        IndexedListJoinColumnBidirectionalRefEdEntity rev3 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed3_id, 3);
+        IndexedListJoinColumnBidirectionalRefEdEntity rev4 = getAuditReader().find(IndexedListJoinColumnBidirectionalRefEdEntity.class, ed3_id, 4);
+
+        assertTrue(rev1.getOwner().equals(ing1));
+        assertTrue(rev2.getOwner().equals(ing1));
+        assertTrue(rev3.getOwner().equals(ing1));
+        assertTrue(rev4.getOwner().equals(ing1));
+
+        assertEquals(rev1.getPosition(), new Integer(2));
+        assertEquals(rev2.getPosition(), new Integer(1));
+        assertEquals(rev3.getPosition(), new Integer(0));
+        assertEquals(rev4.getPosition(), new Integer(1));
+    }
+}
\ No newline at end of file

Copied: core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/JoinColumnBidirectionalList.java (from rev 18236, core/trunk/envers/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/JoinColumnBidirectionalList.java)
===================================================================
--- core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/JoinColumnBidirectionalList.java	                        (rev 0)
+++ core/branches/envers-hibernate-3.3/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/JoinColumnBidirectionalList.java	2009-12-16 11:30:19 UTC (rev 18237)
@@ -0,0 +1,219 @@
+/*
+ * 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+ at 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();
+
+        // No revision - no changes
+        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());
+
+        ed2.setOwner(ing2);
+
+        em.getTransaction().commit();
+        em.clear();
+
+        // Revision 3 (ing1: ed1, ed2)
+        em.getTransaction().begin();
+
+        ed1 = em.find(ListJoinColumnBidirectionalRefEdEntity.class, ed1.getId());
+
+        ed1.setData("ed1 bis");
+        // Shouldn't get written
+        ed1.setOwner(ing2);
+
+        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");
+    }
+}



More information about the hibernate-commits mailing list