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