From a90cf3e5eb1552b20bd888fdf0b921cb2fa0a9ff Mon Sep 17 00:00:00 2001 From: Adam Warski Date: Wed, 16 Dec 2009 11:09:34 +0000 Subject: [PATCH] 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 git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18236 1b8cb986-b30d-0410-93ca-fae66ebed9b2 --- .../configuration/ClassesAuditingData.java | 53 ++++ .../configuration/EntitiesConfigurator.java | 3 + .../metadata/BasicMetadataGenerator.java | 2 +- .../metadata/CollectionMetadataGenerator.java | 80 +++--- .../metadata/IdMetadataGenerator.java | 2 +- .../ToOneRelationMetadataGenerator.java | 33 +-- .../metadata/reader/PropertyAuditingData.java | 13 +- .../envers/entities/EntityConfiguration.java | 16 +- .../envers/entities/RelationDescription.java | 9 +- .../PersistentCollectionChangeData.java | 28 +- .../entities/mapper/SinglePropertyMapper.java | 4 + .../relation/AbstractCollectionMapper.java | 3 +- .../relation/BasicCollectionMapper.java | 4 - .../mapper/relation/ListCollectionMapper.java | 5 - .../mapper/relation/MapCollectionMapper.java | 4 - .../MiddleStraightComponentMapper.java | 56 ++++ .../envers/event/AuditEventListener.java | 3 +- .../FakeBidirectionalRelationWorkUnit.java | 14 +- .../hibernate/envers/tools/MappingTools.java | 17 ++ ...istJoinColumnBidirectionalRefEdEntity.java | 96 +++++++ ...stJoinColumnBidirectionalRefIngEntity.java | 92 +++++++ .../IndexedJoinColumnBidirectionalList.java | 250 ++++++++++++++++++ 22 files changed, 683 insertions(+), 104 deletions(-) create mode 100644 envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java create mode 100644 envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefEdEntity.java create mode 100644 envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefIngEntity.java create mode 100644 envers/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/IndexedJoinColumnBidirectionalList.java diff --git a/envers/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java b/envers/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java index 696b24c7fc..a3c3e3bb25 100644 --- a/envers/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java +++ b/envers/src/main/java/org/hibernate/envers/configuration/ClassesAuditingData.java @@ -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 @@ import java.util.LinkedHashMap; * @author Adam Warski (adam at warski dot org) */ public class ClassesAuditingData { + private static final Logger log = LoggerFactory.getLogger(ClassesAuditingData.class); + private final Map entityNameToAuditingData = new HashMap(); private final Map persistentClassToAuditingData = new LinkedHashMap(); @@ -40,4 +47,50 @@ public class ClassesAuditingData { public ClassAuditingData getClassAuditingData(String entityName) { return entityNameToAuditingData.get(entityName); } + + /** + * After all meta-data is read, updates calculated fields. This includes: + *
    + *
  • setting {@code forceInsertable} to {@code true} for properties specified by {@code @AuditMappedBy}
  • + *
+ */ + public void updateCalculatedFields() { + for (Map.Entry 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); + } + } } diff --git a/envers/src/main/java/org/hibernate/envers/configuration/EntitiesConfigurator.java b/envers/src/main/java/org/hibernate/envers/configuration/EntitiesConfigurator.java index 82f0235f77..97459e7cda 100644 --- a/envers/src/main/java/org/hibernate/envers/configuration/EntitiesConfigurator.java +++ b/envers/src/main/java/org/hibernate/envers/configuration/EntitiesConfigurator.java @@ -80,6 +80,9 @@ public class EntitiesConfigurator { 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); diff --git a/envers/src/main/java/org/hibernate/envers/configuration/metadata/BasicMetadataGenerator.java b/envers/src/main/java/org/hibernate/envers/configuration/metadata/BasicMetadataGenerator.java index 052f930362..9d967383c4 100644 --- a/envers/src/main/java/org/hibernate/envers/configuration/metadata/BasicMetadataGenerator.java +++ b/envers/src/main/java/org/hibernate/envers/configuration/metadata/BasicMetadataGenerator.java @@ -66,7 +66,7 @@ public final class BasicMetadataGenerator { 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) value.getColumnIterator()); } diff --git a/envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java b/envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java index e1d8711a1f..8472af4f47 100644 --- a/envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java +++ b/envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java @@ -43,13 +43,10 @@ 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.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.OneToMany; 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,17 +128,7 @@ public final class CollectionMetadataGenerator { throw new MappingException("Unable to read auditing configuration for " + referencingEntityName + "!"); } - referencedEntityName = 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; - } + referencedEntityName = MappingTools.getReferencedEntityName(propertyValue.getElement()); } void addCollection() { @@ -206,10 +192,8 @@ public final class CollectionMetadataGenerator { 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 @@ public final class CollectionMetadataGenerator { // 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 @@ public final class CollectionMetadataGenerator { if (value.getElement() instanceof OneToMany && !value.isInverse()) { // This must be a @JoinColumn+@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 @@ public final class CollectionMetadataGenerator { 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 @@ public final class CollectionMetadataGenerator { } else { // Last but one parameter: collection components are always insertable boolean mapped = mainGenerator.getBasicMetadataGenerator().addBasic(xmlMapping, - new PropertyAuditingData(prefix, "field", ModificationStore.FULL, RelationTargetAuditMode.AUDITED, null, null), + new PropertyAuditingData(prefix, "field", ModificationStore.FULL, RelationTargetAuditMode.AUDITED, null, null, false), value, null, true, true); if (mapped) { @@ -533,31 +533,14 @@ public final class CollectionMetadataGenerator { return middleEntityXmlId; } - private String getMappedByCommon(PersistentClass referencedClass) { - // If there's an @AuditMappedBy specified, returning it directly. - String auditMappedBy = propertyAuditingData.getAuditMappedBy(); - if (auditMappedBy != null) { - // Checking that the property exists. - try { - referencedClass.getProperty(auditMappedBy); - } catch (MappingException me) { - throw new MappingException("@AuditMappedBy points to a property that can be read: " + - referencedClass.getEntityName() + "." + auditMappedBy, me); - } - - return auditMappedBy; - } - - return null; - } - @SuppressWarnings({"unchecked"}) private String getMappedBy(Collection collectionValue) { PersistentClass referencedClass = ((OneToMany) collectionValue.getElement()).getAssociatedClass(); - 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 assocClassProps = referencedClass.getPropertyIterator(); @@ -577,9 +560,10 @@ public final class CollectionMetadataGenerator { @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 properties = referencedClass.getPropertyIterator(); diff --git a/envers/src/main/java/org/hibernate/envers/configuration/metadata/IdMetadataGenerator.java b/envers/src/main/java/org/hibernate/envers/configuration/metadata/IdMetadataGenerator.java index b905a7e128..8ebbfb3227 100644 --- a/envers/src/main/java/org/hibernate/envers/configuration/metadata/IdMetadataGenerator.java +++ b/envers/src/main/java/org/hibernate/envers/configuration/metadata/IdMetadataGenerator.java @@ -139,6 +139,6 @@ public final class IdMetadataGenerator { 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); } } diff --git a/envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java b/envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java index 9af1de4b79..bd0fdac77d 100644 --- a/envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java +++ b/envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java @@ -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.CompositeMapperBuilder; 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 @@ public final class ToOneRelationMetadataGenerator { // 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 diff --git a/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java b/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java index 2320b1bab7..50b3869f8a 100644 --- a/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java +++ b/envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java @@ -48,13 +48,15 @@ public class PropertyAuditingData { 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 @@ public class PropertyAuditingData { this.relationTargetAuditMode = relationTargetAuditMode; this.auditMappedBy = auditMappedBy; this.positionMappedBy = positionMappedBy; + this.forceInsertable = forceInsertable; } public String getName() { @@ -136,6 +139,14 @@ public class PropertyAuditingData { 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(); diff --git a/envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java b/envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java index 8fadba9fd9..ed45caae34 100644 --- a/envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java +++ b/envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java @@ -53,29 +53,31 @@ public class EntityConfiguration { 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) { diff --git a/envers/src/main/java/org/hibernate/envers/entities/RelationDescription.java b/envers/src/main/java/org/hibernate/envers/entities/RelationDescription.java index 467e324a61..dd1e778d2b 100644 --- a/envers/src/main/java/org/hibernate/envers/entities/RelationDescription.java +++ b/envers/src/main/java/org/hibernate/envers/entities/RelationDescription.java @@ -36,18 +36,21 @@ public class RelationDescription { 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 @@ public class RelationDescription { return fakeBidirectionalRelationMapper; } + public PropertyMapper getFakeBidirectionalRelationIndexMapper() { + return fakeBidirectionalRelationIndexMapper; + } + public boolean isInsertable() { return insertable; } diff --git a/envers/src/main/java/org/hibernate/envers/entities/mapper/PersistentCollectionChangeData.java b/envers/src/main/java/org/hibernate/envers/entities/mapper/PersistentCollectionChangeData.java index f316b58335..a1d1e9a10c 100644 --- a/envers/src/main/java/org/hibernate/envers/entities/mapper/PersistentCollectionChangeData.java +++ b/envers/src/main/java/org/hibernate/envers/entities/mapper/PersistentCollectionChangeData.java @@ -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 @@ public class PersistentCollectionChangeData { } /** - * 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; + } } diff --git a/envers/src/main/java/org/hibernate/envers/entities/mapper/SinglePropertyMapper.java b/envers/src/main/java/org/hibernate/envers/entities/mapper/SinglePropertyMapper.java index a55ce80733..33d5b44678 100644 --- a/envers/src/main/java/org/hibernate/envers/entities/mapper/SinglePropertyMapper.java +++ b/envers/src/main/java/org/hibernate/envers/entities/mapper/SinglePropertyMapper.java @@ -47,6 +47,10 @@ import org.hibernate.HibernateException; public class SinglePropertyMapper implements PropertyMapper, SimpleMapperBuilder { private PropertyData propertyData; + public SinglePropertyMapper(PropertyData propertyData) { + this.propertyData = propertyData; + } + public SinglePropertyMapper() { } public void add(PropertyData propertyData) { diff --git a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java index 6501179a13..7be88b9710 100644 --- a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java +++ b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java @@ -70,7 +70,6 @@ public abstract class AbstractCollectionMapper implements PropertyMapper { 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 @@ public abstract class AbstractCollectionMapper implements PropertyMapper { 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); diff --git a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java index 46a92ae0f6..6c9aa98039 100644 --- a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java +++ b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java @@ -71,8 +71,4 @@ public final class BasicCollectionMapper extends AbstractC protected void mapToMapFromObject(Map data, Object changed) { elementComponentData.getComponentMapper().mapToMapFromObject(data, changed); } - - protected Object getElement(Object changedObject) { - return changedObject; - } } diff --git a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java index fcbb497293..a20a73bfb0 100644 --- a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java +++ b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java @@ -83,9 +83,4 @@ public final class ListCollectionMapper extends AbstractCollectionMapper i elementComponentData.getComponentMapper().mapToMapFromObject(data, indexValuePair.getSecond()); indexComponentData.getComponentMapper().mapToMapFromObject(data, indexValuePair.getFirst()); } - - @SuppressWarnings({"unchecked"}) - protected Object getElement(Object changedObject) { - return ((Pair) changedObject).getFirst(); - } } \ No newline at end of file diff --git a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java index 9a527d1090..3b72ed241c 100644 --- a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java +++ b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java @@ -76,8 +76,4 @@ public final class MapCollectionMapper extends AbstractCollection 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 diff --git a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java new file mode 100644 index 0000000000..ddafe5584d --- /dev/null +++ b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/component/MiddleStraightComponentMapper.java @@ -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 data, + Object dataObject, Number revision) { + return data.get(propertyName); + } + + public void mapToMapFromObject(Map 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 diff --git a/envers/src/main/java/org/hibernate/envers/event/AuditEventListener.java b/envers/src/main/java/org/hibernate/envers/event/AuditEventListener.java index a813bda19e..61e42b0d80 100644 --- a/envers/src/main/java/org/hibernate/envers/event/AuditEventListener.java +++ b/envers/src/main/java/org/hibernate/envers/event/AuditEventListener.java @@ -239,7 +239,8 @@ public class AuditEventListener implements PostInsertEventListener, PostUpdateEv 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. diff --git a/envers/src/main/java/org/hibernate/envers/synchronization/work/FakeBidirectionalRelationWorkUnit.java b/envers/src/main/java/org/hibernate/envers/synchronization/work/FakeBidirectionalRelationWorkUnit.java index 13b1e09e0e..f8eea8f514 100644 --- a/envers/src/main/java/org/hibernate/envers/synchronization/work/FakeBidirectionalRelationWorkUnit.java +++ b/envers/src/main/java/org/hibernate/envers/synchronization/work/FakeBidirectionalRelationWorkUnit.java @@ -28,13 +28,14 @@ public class FakeBidirectionalRelationWorkUnit extends AbstractAuditWorkUnit imp 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(); - 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 @@ public class FakeBidirectionalRelationWorkUnit extends AbstractAuditWorkUnit imp 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 @@ public class FakeBidirectionalRelationWorkUnit extends AbstractAuditWorkUnit imp // 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) { diff --git a/envers/src/main/java/org/hibernate/envers/tools/MappingTools.java b/envers/src/main/java/org/hibernate/envers/tools/MappingTools.java index 28482e3811..5464a1b8e6 100644 --- a/envers/src/main/java/org/hibernate/envers/tools/MappingTools.java +++ b/envers/src/main/java/org/hibernate/envers/tools/MappingTools.java @@ -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 class MappingTools { 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; + } } diff --git a/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefEdEntity.java b/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefEdEntity.java new file mode 100644 index 0000000000..3735d78594 --- /dev/null +++ b/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefEdEntity.java @@ -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) + */ +@Entity +@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 diff --git a/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefIngEntity.java b/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefIngEntity.java new file mode 100644 index 0000000000..d6ad64c913 --- /dev/null +++ b/envers/src/test/java/org/hibernate/envers/test/entities/onetomany/detached/IndexedListJoinColumnBidirectionalRefIngEntity.java @@ -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) + */ +@Entity +@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 references; + + public IndexedListJoinColumnBidirectionalRefIngEntity() { } + + public IndexedListJoinColumnBidirectionalRefIngEntity(Integer id, String data, IndexedListJoinColumnBidirectionalRefEdEntity... references) { + this.id = id; + this.data = data; + this.references = new ArrayList(); + 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 getReferences() { + return references; + } + + public void setReferences(List 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 diff --git a/envers/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/IndexedJoinColumnBidirectionalList.java b/envers/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/IndexedJoinColumnBidirectionalList.java new file mode 100644 index 0000000000..714660cb41 --- /dev/null +++ b/envers/src/test/java/org/hibernate/envers/test/integration/onetomany/detached/IndexedJoinColumnBidirectionalList.java @@ -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+@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