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;
  }
  private String getMappedBy(Collection collectionValue) {
    PersistentClass referencedClass = null;
    if (collectionValue.getElement() instanceof OneToMany) {
      OneToMany oneToManyValue = (OneToMany) collectionValue.getElement();
      referencedClass = oneToManyValue.getAssociatedClass();
    } else if (collectionValue.getElement() instanceof ManyToOne) {
      // Case for bi-directional relation with @JoinTable on the owning @ManyToOne side.
      ManyToOne manyToOneValue = (ManyToOne) collectionValue.getElement();
      referencedClass =
          manyToOneValue.getMappings().getClass(manyToOneValue.getReferencedEntityName());
    }

    // 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 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);
  }
  @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);
  }