[hibernate-commits] Hibernate SVN: r18236 - in core/trunk/envers/src: main/java/org/hibernate/envers/configuration/metadata and 10 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Wed Dec 16 06:09:35 EST 2009


Author: adamw
Date: 2009-12-16 06:09:34 -0500 (Wed, 16 Dec 2009)
New Revision: 18236

Added:
   core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java
   core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefEdEntity.java
   core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefIngEntity.java
   core/trunk/envers/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/IndexedJoinColumnBidirectionalList.java
Modified:
   core/trunk/envers/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java
   core/trunk/envers/src/main/java/org/hibernate/envers/configuration/EntitiesConfigurator.java
   core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/BasicMetadataGenerator.java
   core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java
   core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/IdMetadataGenerator.java
   core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java
   core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java
   core/trunk/envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java
   core/trunk/envers/src/main/java/org/hibernate/envers/entities/RelationDescription.java
   core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/PersistentCollectionChangeData.java
   core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/SinglePropertyMapper.java
   core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java
   core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java
   core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java
   core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java
   core/trunk/envers/src/main/java/org/hibernate/envers/event/AuditEventListener.java
   core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/FakeBidirectionalRelationWorkUnit.java
   core/trunk/envers/src/main/java/org/hibernate/envers/tools/MappingTools.java
Log:
HHH-4694:
- support for indexed "fake" bidirectional relations
- adding a field-calculation-phase, after all metadata is read from annotations, but before any audit entities generation is done

Modified: core/trunk/envers/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -1,7 +1,12 @@
 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;
@@ -13,6 +18,8 @@
  * @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>();
 
@@ -40,4 +47,50 @@
     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/trunk/envers/src/main/java/org/hibernate/envers/configuration/EntitiesConfigurator.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/configuration/EntitiesConfigurator.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/configuration/EntitiesConfigurator.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -80,6 +80,9 @@
             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);
 

Modified: core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/BasicMetadataGenerator.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/BasicMetadataGenerator.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/BasicMetadataGenerator.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -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/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -43,13 +43,10 @@
 import org.hibernate.envers.entities.PropertyData;
 import org.hibernate.envers.entities.mapper.CompositeMapperBuilder;
 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.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.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;
@@ -68,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;
@@ -132,19 +128,9 @@
             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();
 
@@ -206,10 +192,8 @@
                 propertyAuditingData.getPropertyData(),
                 referencingIdData, queryGenerator);
 
-        // Checking the type of the collection and adding an appropriate mapper.
-        addMapper(commonCollectionMapperData, elementComponentData, indexComponentData);
-
         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).
@@ -225,13 +209,29 @@
                     // 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(), fakeBidirectionalRelationMapper);
+                referencedEntityName, referencingIdData.getPrefixedMapper(), fakeBidirectionalRelationMapper,
+                fakeBidirectionalRelationIndexMapper);
     }
 
     /**
@@ -257,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();
@@ -421,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);
@@ -447,7 +447,7 @@
         } else {
             // Last but one parameter: collection components are always insertable
             boolean mapped = mainGenerator.getBasicMetadataGenerator().addBasic(xmlMapping,
-                    new PropertyAuditingData(prefix, "field", ModificationStore.FULL, RelationTargetAuditMode.AUDITED, null, null),
+                    new PropertyAuditingData(prefix, "field", ModificationStore.FULL, RelationTargetAuditMode.AUDITED, null, null, false),
                     value, null, true, true);
 
             if (mapped) {
@@ -533,33 +533,16 @@
         return middleEntityXmlId;
     }
 
-    private String getMappedByCommon(PersistentClass referencedClass) {
+    @SuppressWarnings({"unchecked"})
+    private String getMappedBy(Collection collectionValue) {
+        PersistentClass referencedClass = ((OneToMany) collectionValue.getElement()).getAssociatedClass();
+
         // If there's an @AuditMappedBy specified, returning it directly.
         String auditMappedBy = propertyAuditingData.getAuditMappedBy();
         if (auditMappedBy != null) {
-            // Checking that the property exists.
-            try {
-                referencedClass.getProperty(auditMappedBy);
-            } catch (MappingException me) {
-                throw new MappingException("@AuditMappedBy points to a property that can be read: " +
-                    referencedClass.getEntityName() + "." + auditMappedBy, me);
-            }
-
             return auditMappedBy;
         }
 
-        return null;
-    }
-
-    @SuppressWarnings({"unchecked"})
-    private String getMappedBy(Collection collectionValue) {
-        PersistentClass referencedClass = ((OneToMany) collectionValue.getElement()).getAssociatedClass();
-
-        String mappedByCommon = getMappedByCommon(referencedClass);
-        if (mappedByCommon != null) {
-            return mappedByCommon;
-        }
-
         Iterator<Property> assocClassProps = referencedClass.getPropertyIterator();
 
         while (assocClassProps.hasNext()) {
@@ -577,9 +560,10 @@
 
     @SuppressWarnings({"unchecked"})
     private String getMappedBy(Table collectionTable, PersistentClass referencedClass) {
-        String mappedByCommon = getMappedByCommon(referencedClass);
-        if (mappedByCommon != null) {
-            return mappedByCommon;
+        // If there's an @AuditMappedBy specified, returning it directly.
+        String auditMappedBy = propertyAuditingData.getAuditMappedBy();
+        if (auditMappedBy != null) {
+            return auditMappedBy;
         }
 
         Iterator<Property> properties = referencedClass.getPropertyIterator();

Modified: core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/IdMetadataGenerator.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/IdMetadataGenerator.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/IdMetadataGenerator.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -139,6 +139,6 @@
 
     private PropertyAuditingData getIdPersistentPropertyAuditingData(Property property) {
         return new PropertyAuditingData(property.getName(), property.getPropertyAccessorName(),
-                ModificationStore.FULL, RelationTargetAuditMode.AUDITED, null, null);
+                ModificationStore.FULL, RelationTargetAuditMode.AUDITED, null, null, false);
     }
 }

Modified: core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -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,24 +33,16 @@
 import org.hibernate.envers.entities.mapper.id.IdMapper;
 import org.hibernate.envers.entities.mapper.relation.OneToOneNotOwningMapper;
 import org.hibernate.envers.entities.mapper.relation.ToOneIdMapper;
-import org.hibernate.envers.configuration.metadata.reader.PropertyAuditingData;
-import org.hibernate.envers.configuration.metadata.reader.ClassAuditingData;
 import org.hibernate.envers.tools.MappingTools;
-
-import org.hibernate.MappingException;
 import org.hibernate.mapping.OneToOne;
 import org.hibernate.mapping.ToOne;
 import org.hibernate.mapping.Value;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Generates metadata for to-one relations (reference-valued properties).
  * @author Adam Warski (adam at warski dot org)
  */
 public final class ToOneRelationMetadataGenerator {
-    private static final Logger log = LoggerFactory.getLogger(ToOneRelationMetadataGenerator.class);
-
     private final AuditMetadataGenerator mainGenerator;
 
     ToOneRelationMetadataGenerator(AuditMetadataGenerator auditMetadataGenerator) {
@@ -79,23 +73,12 @@
         // 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 = false;
-        if (!insertable) {
-            ClassAuditingData referencedAuditingData = mainGenerator.getClassesAuditingData().getClassAuditingData(referencedEntityName);
-
-            // Looking through the properties of the referenced entity to find the right property.
-            for (String referencedPropertyName : referencedAuditingData.getPropertyNames()) {
-                String auditMappedBy = referencedAuditingData.getPropertyAuditingData(referencedPropertyName).getAuditMappedBy();
-                if (propertyAuditingData.getName().equals(auditMappedBy)) {
-                    log.debug("Non-insertable property " + entityName + "." + propertyAuditingData.getName() +
-                            " will be made insertable because a matching @AuditMappedBy was found in the " +
-                            referencedEntityName + " entity.");
-
-                    insertable = true;
-                    nonInsertableFake = true;
-                    break;
-                }
-            }
+        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

Modified: core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -48,13 +48,15 @@
 	private RelationTargetAuditMode relationTargetAuditMode;
     private String auditMappedBy;
     private String positionMappedBy;
+    private boolean forceInsertable;
 
 	public PropertyAuditingData() {
     }
 
     public PropertyAuditingData(String name, String accessType, ModificationStore store,
 								RelationTargetAuditMode relationTargetAuditMode,
-                                String auditMappedBy, String positionMappedBy) {
+                                String auditMappedBy, String positionMappedBy,
+                                boolean forceInsertable) {
         this.name = name;
 		this.beanName = name;
         this.accessType = accessType;
@@ -62,6 +64,7 @@
 		this.relationTargetAuditMode = relationTargetAuditMode;
         this.auditMappedBy = auditMappedBy;
         this.positionMappedBy = positionMappedBy;
+        this.forceInsertable = forceInsertable;
     }
 
 	public String getName() {
@@ -136,6 +139,14 @@
         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();

Modified: core/trunk/envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -53,29 +53,31 @@
 
     public void addToOneRelation(String fromPropertyName, String toEntityName, IdMapper idMapper, boolean insertable) {
         relations.put(fromPropertyName, new RelationDescription(fromPropertyName, RelationType.TO_ONE,
-                toEntityName, null, idMapper, null, insertable));
+                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, null, true));
+                toEntityName, mappedByPropertyName, idMapper, null, null, true));
     }
 
     public void addToManyNotOwningRelation(String fromPropertyName, String mappedByPropertyName, String toEntityName,
-                                     IdMapper idMapper, PropertyMapper fakeBidirectionalRelationMapper) {
+                                           IdMapper idMapper, PropertyMapper fakeBidirectionalRelationMapper,
+                                           PropertyMapper fakeBidirectionalRelationIndexMapper) {
         relations.put(fromPropertyName, new RelationDescription(fromPropertyName, RelationType.TO_MANY_NOT_OWNING,
-                toEntityName, mappedByPropertyName, idMapper, fakeBidirectionalRelationMapper, true));
+                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, null, true));
+                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, null, true));
+                toEntityName, mappedByPropertyName, null, null, null, true));
     }
 
     public boolean isRelation(String propertyName) {

Modified: core/trunk/envers/src/main/java/org/hibernate/envers/entities/RelationDescription.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/entities/RelationDescription.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/entities/RelationDescription.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -36,18 +36,21 @@
     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,
-                               PropertyMapper fakeBidirectionalRelationMapper, boolean insertable) {
+                               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;
@@ -77,6 +80,10 @@
         return fakeBidirectionalRelationMapper;
     }
 
+    public PropertyMapper getFakeBidirectionalRelationIndexMapper() {
+        return fakeBidirectionalRelationIndexMapper;
+    }
+
     public boolean isInsertable() {
         return insertable;
     }

Modified: core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/PersistentCollectionChangeData.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/PersistentCollectionChangeData.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/PersistentCollectionChangeData.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -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/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/SinglePropertyMapper.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/SinglePropertyMapper.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/SinglePropertyMapper.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -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/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -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/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -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/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -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/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -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

Copied: core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java (from rev 18224, core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleSimpleComponentMapper.java)
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java	                        (rev 0)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -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/trunk/envers/src/main/java/org/hibernate/envers/event/AuditEventListener.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/event/AuditEventListener.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/event/AuditEventListener.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -239,7 +239,8 @@
                     relatedId, relatedObj);
 
             verSync.addWorkUnit(new FakeBidirectionalRelationWorkUnit(event.getSession(), relatedEntityName, verCfg,
-                    relatedId, referencingPropertyName, event.getAffectedOwnerOrNull(), rd, revType, nestedWorkUnit));
+                    relatedId, referencingPropertyName, event.getAffectedOwnerOrNull(), rd, revType,
+                    changeData.getChangedElementIndex(), nestedWorkUnit));
         }
 
         // We also have to generate a collection change work unit for the owning entity.

Modified: core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/FakeBidirectionalRelationWorkUnit.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/FakeBidirectionalRelationWorkUnit.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/FakeBidirectionalRelationWorkUnit.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -28,13 +28,14 @@
                                              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));
+        fakeRelationChanges.put(referencingPropertyName, new FakeRelationChange(owningEntity, rd, revisionType, index));
     }
 
     public FakeBidirectionalRelationWorkUnit(FakeBidirectionalRelationWorkUnit original,
@@ -134,11 +135,14 @@
         private final Object owningEntity;
         private final RelationDescription rd;
         private final RevisionType revisionType;
+        private final Object index;
 
-        public FakeRelationChange(Object owningEntity, RelationDescription rd, RevisionType revisionType) {
+        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() {
@@ -150,6 +154,12 @@
             // 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) {

Modified: core/trunk/envers/src/main/java/org/hibernate/envers/tools/MappingTools.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/tools/MappingTools.java	2009-12-16 08:23:41 UTC (rev 18235)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/tools/MappingTools.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -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)
  */
@@ -20,4 +25,16 @@
     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/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefEdEntity.java (from rev 18224, core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefEdEntity.java)
===================================================================
--- core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefEdEntity.java	                        (rev 0)
+++ core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefEdEntity.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -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/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefIngEntity.java (from rev 18224, core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/ListJoinColumnBidirectionalRefIngEntity.java)
===================================================================
--- core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefIngEntity.java	                        (rev 0)
+++ core/trunk/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefIngEntity.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -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/trunk/envers/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/IndexedJoinColumnBidirectionalList.java (from rev 18226, core/trunk/envers/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/JoinColumnBidirectionalList.java)
===================================================================
--- core/trunk/envers/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/IndexedJoinColumnBidirectionalList.java	                        (rev 0)
+++ core/trunk/envers/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/IndexedJoinColumnBidirectionalList.java	2009-12-16 11:09:34 UTC (rev 18236)
@@ -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



More information about the hibernate-commits mailing list