/* * 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.configuration.metadata; import org.dom4j.Element; import org.hibernate.MappingException; import org.hibernate.envers.ModificationStore; import org.hibernate.envers.RelationTargetAuditMode; import org.hibernate.envers.configuration.metadata.reader.PropertyAuditingData; import org.hibernate.envers.entities.EntityConfiguration; import org.hibernate.envers.entities.IdMappingData; import org.hibernate.envers.entities.PropertyData; import org.hibernate.envers.entities.mapper.CompositeMapperBuilder; import org.hibernate.envers.entities.mapper.PropertyMapper; import org.hibernate.envers.entities.mapper.SinglePropertyMapper; import org.hibernate.envers.entities.mapper.id.IdMapper; import org.hibernate.envers.entities.mapper.relation.*; import org.hibernate.envers.entities.mapper.relation.component.*; import org.hibernate.envers.entities.mapper.relation.lazy.proxy.*; import org.hibernate.envers.entities.mapper.relation.query.OneAuditEntityQueryGenerator; import org.hibernate.envers.entities.mapper.relation.query.RelationQueryGenerator; import org.hibernate.envers.internal.EnversMessageLogger; import org.hibernate.envers.tools.MappingTools; import org.hibernate.envers.tools.StringTools; import org.hibernate.envers.tools.Tools; import org.hibernate.mapping.Collection; import org.hibernate.mapping.*; import org.hibernate.type.*; import org.jboss.logging.Logger; import javax.persistence.JoinColumn; import java.util.*; import java.util.List; import java.util.Map; import java.util.Set; /** * Generates metadata for a collection-valued property. * @author Adam Warski (adam at warski dot org) * @author Hern�n Chanfreau */ public final class CollectionMetadataGenerator { public static final EnversMessageLogger LOG = Logger.getMessageLogger(EnversMessageLogger.class, CollectionMetadataGenerator.class.getName()); private final AuditMetadataGenerator mainGenerator; private final String propertyName; private final Collection propertyValue; private final CompositeMapperBuilder currentMapper; private final String referencingEntityName; private final EntityXmlMappingData xmlMappingData; private final PropertyAuditingData propertyAuditingData; private final EntityConfiguration referencingEntityConfiguration; /** * Null if this collection isn't a relation to another entity. */ private final String referencedEntityName; /** * @param mainGenerator Main generator, giving access to configuration and the basic mapper. * @param propertyValue Value of the collection, as mapped by Hibernate. * @param currentMapper Mapper, to which the appropriate {@link org.hibernate.envers.entities.mapper.PropertyMapper} * will be added. * @param referencingEntityName Name of the entity that owns this collection. * @param xmlMappingData In case this collection requires a middle table, additional mapping documents will * be created using this object. * @param propertyAuditingData Property auditing (meta-)data. Among other things, holds the name of the * property that references the collection in the referencing entity, the user data for middle (join) * table and the value of the <code>@MapKey</code> annotation, if there was one. */ public CollectionMetadataGenerator(AuditMetadataGenerator mainGenerator, Collection propertyValue, CompositeMapperBuilder currentMapper, String referencingEntityName, EntityXmlMappingData xmlMappingData, PropertyAuditingData propertyAuditingData) { this.mainGenerator = mainGenerator; this.propertyValue = propertyValue; this.currentMapper = currentMapper; this.referencingEntityName = referencingEntityName; this.xmlMappingData = xmlMappingData; this.propertyAuditingData = propertyAuditingData; this.propertyName = propertyAuditingData.getName(); referencingEntityConfiguration = mainGenerator.getEntitiesConfigurations().get(referencingEntityName); if (referencingEntityConfiguration == null) { throw new MappingException("Unable to read auditing configuration for " + referencingEntityName + "!"); } referencedEntityName = MappingTools.getReferencedEntityName(propertyValue.getElement()); } void addCollection() { Type type = propertyValue.getType(); boolean oneToManyAttachedType = type instanceof BagType || type instanceof SetType || type instanceof MapType || type instanceof ListType; boolean inverseOneToMany = (propertyValue.getElement() instanceof OneToMany) && (propertyValue.isInverse()); boolean fakeOneToManyBidirectional = (propertyValue.getElement() instanceof OneToMany) && (propertyAuditingData.getAuditMappedBy() != null); if (oneToManyAttachedType && (inverseOneToMany || fakeOneToManyBidirectional)) { // A one-to-many relation mapped using @ManyToOne and @OneToMany(mappedBy="...") addOneToManyAttached(fakeOneToManyBidirectional); } else { // All other kinds of relations require a middle (join) table. addWithMiddleTable(); } } private MiddleIdData createMiddleIdData(IdMappingData idMappingData, String prefix, String entityName) { return new MiddleIdData(mainGenerator.getVerEntCfg(), idMappingData, prefix, entityName, mainGenerator.getEntitiesConfigurations().containsKey(entityName)); } @SuppressWarnings({"unchecked"}) private void addOneToManyAttached(boolean fakeOneToManyBidirectional) { LOG.debugf("Adding audit mapping for property %s.%s: one-to-many collection, using a join column on the referenced entity", referencingEntityName, propertyName); String mappedBy = getMappedBy(propertyValue); IdMappingData referencedIdMapping = mainGenerator.getReferencedIdMappingData(referencingEntityName, referencedEntityName, propertyAuditingData, false); IdMappingData referencingIdMapping = referencingEntityConfiguration.getIdMappingData(); // Generating the id mappers data for the referencing side of the relation. MiddleIdData referencingIdData = createMiddleIdData(referencingIdMapping, mappedBy + "_", referencingEntityName); // And for the referenced side. The prefixed mapper won't be used (as this collection isn't persisted // in a join table, so the prefix value is arbitrary). MiddleIdData referencedIdData = createMiddleIdData(referencedIdMapping, null, referencedEntityName); // Generating the element mapping. MiddleComponentData elementComponentData = new MiddleComponentData( new MiddleRelatedComponentMapper(referencedIdData), 0); // Generating the index mapping, if an index exists. It can only exists in case a javax.persistence.MapKey // annotation is present on the entity. So the middleEntityXml will be not be used. The queryGeneratorBuilder // will only be checked for nullnes. MiddleComponentData indexComponentData = addIndex(null, null); // Generating the query generator - it should read directly from the related entity. RelationQueryGenerator queryGenerator = new OneAuditEntityQueryGenerator(mainGenerator.getGlobalCfg(), mainGenerator.getVerEntCfg(), mainGenerator.getAuditStrategy(), referencingIdData, referencedEntityName, referencedIdData); // Creating common mapper data. CommonCollectionMapperData commonCollectionMapperData = new CommonCollectionMapperData( mainGenerator.getVerEntCfg(), referencedEntityName, propertyAuditingData.getPropertyData(), referencingIdData, queryGenerator); PropertyMapper fakeBidirectionalRelationMapper; PropertyMapper fakeBidirectionalRelationIndexMapper; if (fakeOneToManyBidirectional) { // In case of a fake many-to-one bidirectional relation, we have to generate a mapper which maps // the mapped-by property name to the id of the related entity (which is the owner of the collection). String auditMappedBy = propertyAuditingData.getAuditMappedBy(); // Creating a prefixed relation mapper. IdMapper relMapper = referencingIdMapping.getIdMapper().prefixMappedProperties( MappingTools.createToOneRelationPrefix(auditMappedBy)); fakeBidirectionalRelationMapper = new ToOneIdMapper( relMapper, // The mapper will only be used to map from entity to map, so no need to provide other details // when constructing the PropertyData. new PropertyData(auditMappedBy, null, null, null), referencingEntityName, 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, fakeBidirectionalRelationIndexMapper); } /** * Adds mapping of the id of a related entity to the given xml mapping, prefixing the id with the given prefix. * @param xmlMapping Mapping, to which to add the xml. * @param prefix Prefix for the names of properties which will be prepended to properties that form the id. * @param columnNameIterator Iterator over the column names that will be used for properties that form the id. * @param relatedIdMapping Id mapping data of the related entity. */ @SuppressWarnings({"unchecked"}) private void addRelatedToXmlMapping(Element xmlMapping, String prefix, MetadataTools.ColumnNameIterator columnNameIterator, IdMappingData relatedIdMapping) { Element properties = (Element) relatedIdMapping.getXmlRelationMapping().clone(); MetadataTools.prefixNamesInPropertyElement(properties, prefix, columnNameIterator, true, true); for (Element idProperty : (java.util.List<Element>) properties.elements()) { xmlMapping.add((Element) idProperty.clone()); } } private String getMiddleTableName(Collection value, String entityName) { // We check how Hibernate maps the collection. 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(MappingTools.getReferencedEntityName(value.getElement())); } // Hibernate uses a middle table for mapping this relation, so we get it's name directly. return value.getCollectionTable().getName(); } @SuppressWarnings({"unchecked"}) private void addWithMiddleTable() { LOG.debugf("Adding audit mapping for property %s.%s: collection with a join table", referencingEntityName, propertyName); // Generating the name of the middle table String auditMiddleTableName; String auditMiddleEntityName; if (!StringTools.isEmpty(propertyAuditingData.getJoinTable().name())) { auditMiddleTableName = propertyAuditingData.getJoinTable().name(); auditMiddleEntityName = propertyAuditingData.getJoinTable().name(); } else { String middleTableName = getMiddleTableName(propertyValue, referencingEntityName); auditMiddleTableName = mainGenerator.getVerEntCfg().getAuditTableName(null, middleTableName); auditMiddleEntityName = mainGenerator.getVerEntCfg().getAuditEntityName(middleTableName); } LOG.debugf("Using join table name: %s", auditMiddleTableName); // Generating the XML mapping for the middle entity, only if the relation isn't inverse. // If the relation is inverse, will be later checked by comparing middleEntityXml with null. Element middleEntityXml; if (!propertyValue.isInverse()) { // Generating a unique middle entity name auditMiddleEntityName = mainGenerator.getAuditEntityNameRegister().createUnique(auditMiddleEntityName); // Registering the generated name mainGenerator.getAuditEntityNameRegister().register(auditMiddleEntityName); middleEntityXml = createMiddleEntityXml(auditMiddleTableName, auditMiddleEntityName, propertyValue.getWhere()); } else { middleEntityXml = null; } // ****** // Generating the mapping for the referencing entity (it must be an entity). // ****** // Getting the id-mapping data of the referencing entity (the entity that "owns" this collection). IdMappingData referencingIdMapping = referencingEntityConfiguration.getIdMappingData(); // Only valid for an inverse relation; null otherwise. String mappedBy; // The referencing prefix is always for a related entity. So it has always the "_" at the end added. String referencingPrefixRelated; String referencedPrefix; if (propertyValue.isInverse()) { // If the relation is inverse, then referencedEntityName is not null. mappedBy = getMappedBy(propertyValue.getCollectionTable(), mainGenerator.getCfg().getClassMapping(referencedEntityName)); referencingPrefixRelated = mappedBy + "_"; referencedPrefix = StringTools.getLastComponent(referencedEntityName); } else { mappedBy = null; referencingPrefixRelated = StringTools.getLastComponent(referencingEntityName) + "_"; referencedPrefix = referencedEntityName == null ? "element" : propertyName; } // Storing the id data of the referencing entity: original mapper, prefixed mapper and entity name. MiddleIdData referencingIdData = createMiddleIdData(referencingIdMapping, referencingPrefixRelated, referencingEntityName); // Creating a query generator builder, to which additional id data will be added, in case this collection // references some entities (either from the element or index). At the end, this will be used to build // a query generator to read the raw data collection from the middle table. QueryGeneratorBuilder queryGeneratorBuilder = new QueryGeneratorBuilder(mainGenerator.getGlobalCfg(), mainGenerator.getVerEntCfg(), mainGenerator.getAuditStrategy(), referencingIdData, auditMiddleEntityName); // Adding the XML mapping for the referencing entity, if the relation isn't inverse. if (middleEntityXml != null) { // Adding related-entity (in this case: the referencing's entity id) id mapping to the xml. addRelatedToXmlMapping(middleEntityXml, referencingPrefixRelated, MetadataTools.getColumnNameIterator(propertyValue.getKey().getColumnIterator()), referencingIdMapping); } // ****** // Generating the element mapping. // ****** MiddleComponentData elementComponentData = addValueToMiddleTable(propertyValue.getElement(), middleEntityXml, queryGeneratorBuilder, referencedPrefix, propertyAuditingData.getJoinTable().inverseJoinColumns()); // ****** // Generating the index mapping, if an index exists. // ****** MiddleComponentData indexComponentData = addIndex(middleEntityXml, queryGeneratorBuilder); // ****** // Generating the property mapper. // ****** // Building the query generator. RelationQueryGenerator queryGenerator = queryGeneratorBuilder.build(elementComponentData, indexComponentData); // Creating common data CommonCollectionMapperData commonCollectionMapperData = new CommonCollectionMapperData( mainGenerator.getVerEntCfg(), auditMiddleEntityName, propertyAuditingData.getPropertyData(), referencingIdData, queryGenerator); // Checking the type of the collection and adding an appropriate mapper. addMapper(commonCollectionMapperData, elementComponentData, indexComponentData); // ****** // Storing information about this relation. // ****** storeMiddleEntityRelationInformation(mappedBy); } private MiddleComponentData addIndex(Element middleEntityXml, QueryGeneratorBuilder queryGeneratorBuilder) { if (propertyValue instanceof IndexedCollection) { IndexedCollection indexedValue = (IndexedCollection) propertyValue; String mapKey = propertyAuditingData.getMapKey(); if (mapKey == null) { // This entity doesn't specify a javax.persistence.MapKey. Mapping it to the middle entity. return addValueToMiddleTable(indexedValue.getIndex(), middleEntityXml, queryGeneratorBuilder, "mapkey", null); } else { IdMappingData referencedIdMapping = mainGenerator.getEntitiesConfigurations() .get(referencedEntityName).getIdMappingData(); int currentIndex = queryGeneratorBuilder == null ? 0 : queryGeneratorBuilder.getCurrentIndex(); if ("".equals(mapKey)) { // The key of the map is the id of the entity. return new MiddleComponentData(new MiddleMapKeyIdComponentMapper(mainGenerator.getVerEntCfg(), referencedIdMapping.getIdMapper()), currentIndex); } else { // The key of the map is a property of the entity. return new MiddleComponentData(new MiddleMapKeyPropertyComponentMapper(mapKey, propertyAuditingData.getAccessType()), currentIndex); } } } else { // No index - creating a dummy mapper. return new MiddleComponentData(new MiddleDummyComponentMapper(), 0); } } /** * * @param value Value, which should be mapped to the middle-table, either as a relation to another entity, * or as a simple value. * @param xmlMapping If not <code>null</code>, xml mapping for this value is added to this element. * @param queryGeneratorBuilder In case <code>value</code> is a relation to another entity, information about it * should be added to the given. * @param prefix Prefix for proeprty names of related entities identifiers. * @param joinColumns Names of columns to use in the xml mapping, if this array isn't null and has any elements. * @return Data for mapping this component. */ @SuppressWarnings({"unchecked"}) private MiddleComponentData addValueToMiddleTable(Value value, Element xmlMapping, QueryGeneratorBuilder queryGeneratorBuilder, String prefix, JoinColumn[] joinColumns) { Type type = value.getType(); if (type instanceof ManyToOneType) { String prefixRelated = prefix + "_"; String referencedEntityName = MappingTools.getReferencedEntityName(value); IdMappingData referencedIdMapping = mainGenerator.getReferencedIdMappingData(referencingEntityName, referencedEntityName, propertyAuditingData, true); // Adding related-entity (in this case: the referenced entities id) id mapping to the xml only if the // relation isn't inverse (so when <code>xmlMapping</code> is not null). if (xmlMapping != null) { addRelatedToXmlMapping(xmlMapping, prefixRelated, joinColumns != null && joinColumns.length > 0 ? MetadataTools.getColumnNameIterator(joinColumns) : MetadataTools.getColumnNameIterator(value.getColumnIterator()), referencedIdMapping); } // Storing the id data of the referenced entity: original mapper, prefixed mapper and entity name. MiddleIdData referencedIdData = createMiddleIdData(referencedIdMapping, prefixRelated, referencedEntityName); // And adding it to the generator builder. queryGeneratorBuilder.addRelation(referencedIdData); return new MiddleComponentData(new MiddleRelatedComponentMapper(referencedIdData), queryGeneratorBuilder.getCurrentIndex()); } 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, false), value, null, true, true); if (mapped) { // Simple values are always stored in the first item of the array returned by the query generator. return new MiddleComponentData(new MiddleSimpleComponentMapper(mainGenerator.getVerEntCfg(), prefix), 0); } else { mainGenerator.throwUnsupportedTypeException(type, referencingEntityName, propertyName); // Impossible to get here. throw new AssertionError(); } } } private void addMapper(CommonCollectionMapperData commonCollectionMapperData, MiddleComponentData elementComponentData, MiddleComponentData indexComponentData) { Type type = propertyValue.getType(); if (type instanceof SortedSetType) { currentMapper.addComposite(propertyAuditingData.getPropertyData(), new SortedSetCollectionMapper(commonCollectionMapperData, TreeSet.class, SortedSetProxy.class, elementComponentData, propertyValue.getComparator())); } else if (type instanceof SetType) { currentMapper.addComposite(propertyAuditingData.getPropertyData(), new BasicCollectionMapper<Set>(commonCollectionMapperData, HashSet.class, SetProxy.class, elementComponentData)); } else if (type instanceof SortedMapType) { // Indexed collection, so <code>indexComponentData</code> is not null. currentMapper.addComposite(propertyAuditingData.getPropertyData(), new SortedMapCollectionMapper(commonCollectionMapperData, TreeMap.class, SortedMapProxy.class, elementComponentData, indexComponentData, propertyValue.getComparator())); } else if (type instanceof MapType) { // Indexed collection, so <code>indexComponentData</code> is not null. currentMapper.addComposite(propertyAuditingData.getPropertyData(), new MapCollectionMapper<Map>(commonCollectionMapperData, HashMap.class, MapProxy.class, elementComponentData, indexComponentData)); } else if (type instanceof BagType) { currentMapper.addComposite(propertyAuditingData.getPropertyData(), new BasicCollectionMapper<List>(commonCollectionMapperData, ArrayList.class, ListProxy.class, elementComponentData)); } else if (type instanceof ListType) { // Indexed collection, so <code>indexComponentData</code> is not null. currentMapper.addComposite(propertyAuditingData.getPropertyData(), new ListCollectionMapper(commonCollectionMapperData, elementComponentData, indexComponentData)); } else { mainGenerator.throwUnsupportedTypeException(type, referencingEntityName, propertyName); } } private void storeMiddleEntityRelationInformation(String mappedBy) { // Only if this is a relation (when there is a referenced entity). if (referencedEntityName != null) { if (propertyValue.isInverse()) { referencingEntityConfiguration.addToManyMiddleNotOwningRelation(propertyName, mappedBy, referencedEntityName); } else { referencingEntityConfiguration.addToManyMiddleRelation(propertyName, referencedEntityName); } } } private Element createMiddleEntityXml(String auditMiddleTableName, String auditMiddleEntityName, String where) { String schema = mainGenerator.getSchema(propertyAuditingData.getJoinTable().schema(), propertyValue.getCollectionTable()); String catalog = mainGenerator.getCatalog(propertyAuditingData.getJoinTable().catalog(), propertyValue.getCollectionTable()); Element middleEntityXml = MetadataTools.createEntity(xmlMappingData.newAdditionalMapping(), new AuditTableData(auditMiddleEntityName, auditMiddleTableName, schema, catalog), null); Element middleEntityXmlId = middleEntityXml.addElement("composite-id"); // If there is a where clause on the relation, adding it to the middle entity. if (where != null) { middleEntityXml.addAttribute("where", where); } middleEntityXmlId.addAttribute("name", mainGenerator.getVerEntCfg().getOriginalIdPropName()); // Adding the revision number as a foreign key to the revision info entity to the composite id of the // middle table. mainGenerator.addRevisionInfoRelation(middleEntityXmlId); // Adding the revision type property to the entity xml. mainGenerator.addRevisionType(middleEntityXml); // All other properties should also be part of the primary key of the middle entity. return middleEntityXmlId; } 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) { return auditMappedBy; } // searching in referenced class String mappedBy = this.searchMappedBy(referencedClass, collectionValue); if(mappedBy == null) { LOG.debugf("Going to search the mapped by attribute for %s in superclasses of entity: %s", propertyName, referencedClass.getClassName()); PersistentClass tempClass = referencedClass; while ((mappedBy == null) && (tempClass.getSuperclass() != null)) { LOG.debugf("Searching in superclass: %s", tempClass.getSuperclass().getClassName()); mappedBy = this.searchMappedBy(tempClass.getSuperclass(), collectionValue); tempClass = tempClass.getSuperclass(); } } if(mappedBy == null) { throw new MappingException("Unable to read the mapped by attribute for " + propertyName + " in " + referencedClass.getClassName() + "!"); } return mappedBy; } @SuppressWarnings({"unchecked"}) private String searchMappedBy(PersistentClass referencedClass, Collection collectionValue) { Iterator<Property> assocClassProps = referencedClass.getPropertyIterator(); while (assocClassProps.hasNext()) { Property property = assocClassProps.next(); if (Tools.iteratorsContentEqual(property.getValue().getColumnIterator(), collectionValue.getKey().getColumnIterator())) { return property.getName(); } } return null; } private String getMappedBy(Table collectionTable, PersistentClass referencedClass) { // If there's an @AuditMappedBy specified, returning it directly. String auditMappedBy = propertyAuditingData.getAuditMappedBy(); if (auditMappedBy != null) { return auditMappedBy; } // searching in referenced class String mappedBy = this.searchMappedBy(referencedClass, collectionTable); // not found on referenced class, searching on superclasses if(mappedBy == null) { LOG.debugf("Going to search the mapped by attribute for %s in superclasses of entity: %s", propertyName, referencedClass.getClassName()); PersistentClass tempClass = referencedClass; while ((mappedBy == null) && (tempClass.getSuperclass() != null)) { LOG.debugf("Searching in superclass: %s", tempClass.getSuperclass().getClassName()); mappedBy = this.searchMappedBy(tempClass.getSuperclass(), collectionTable); tempClass = tempClass.getSuperclass(); } } if(mappedBy == null) { throw new MappingException("Unable to read the mapped by attribute for " + propertyName + " in " + referencedClass.getClassName() + "!"); } return mappedBy; } @SuppressWarnings({"unchecked"}) private String searchMappedBy(PersistentClass referencedClass, Table collectionTable) { Iterator<Property> properties = referencedClass.getPropertyIterator(); while (properties.hasNext()) { Property property = properties.next(); if (property.getValue() instanceof Collection) { // The equality is intentional. We want to find a collection property with the same collection table. //noinspection ObjectEquality if (((Collection) property.getValue()).getCollectionTable() == collectionTable) { return property.getName(); } } } return null; } }