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);
   }
 }
  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 MiddleIdData createMiddleIdData(
     IdMappingData idMappingData, String prefix, String entityName) {
   return new MiddleIdData(
       mainGenerator.getVerEntCfg(),
       idMappingData,
       prefix,
       entityName,
       mainGenerator.getEntitiesConfigurations().containsKey(entityName));
 }
  /**
   * @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 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);
  }
  @SuppressWarnings({"unchecked"})
  IdMappingData addId(PersistentClass pc, boolean audited) {
    // Xml mapping which will be used for relations
    Element rel_id_mapping = new DefaultElement("properties");
    // Xml mapping which will be used for the primary key of the versions table
    Element orig_id_mapping = new DefaultElement("composite-id");

    Property id_prop = pc.getIdentifierProperty();
    Component id_mapper = pc.getIdentifierMapper();

    // Checking if the id mapping is supported
    if (id_mapper == null && id_prop == null) {
      return null;
    }

    SimpleIdMapperBuilder mapper;
    if (id_mapper != null) {
      // Multiple id

      mapper = new MultipleIdMapper(((Component) pc.getIdentifier()).getComponentClassName());
      if (!addIdProperties(
          rel_id_mapping,
          (Iterator<Property>) id_mapper.getPropertyIterator(),
          mapper,
          false,
          audited)) {
        return null;
      }

      // null mapper - the mapping where already added the first time, now we only want to generate
      // the xml
      if (!addIdProperties(
          orig_id_mapping,
          (Iterator<Property>) id_mapper.getPropertyIterator(),
          null,
          true,
          audited)) {
        return null;
      }
    } else if (id_prop.isComposite()) {
      // Embedded id

      Component id_component = (Component) id_prop.getValue();

      mapper =
          new EmbeddedIdMapper(getIdPropertyData(id_prop), id_component.getComponentClassName());
      if (!addIdProperties(
          rel_id_mapping,
          (Iterator<Property>) id_component.getPropertyIterator(),
          mapper,
          false,
          audited)) {
        return null;
      }

      // null mapper - the mapping where already added the first time, now we only want to generate
      // the xml
      if (!addIdProperties(
          orig_id_mapping,
          (Iterator<Property>) id_component.getPropertyIterator(),
          null,
          true,
          audited)) {
        return null;
      }
    } else {
      // Single id

      mapper = new SingleIdMapper();

      // Last but one parameter: ids are always insertable
      mainGenerator
          .getBasicMetadataGenerator()
          .addBasic(
              rel_id_mapping,
              getIdPersistentPropertyAuditingData(id_prop),
              id_prop.getValue(),
              mapper,
              true,
              false);

      // null mapper - the mapping where already added the first time, now we only want to generate
      // the xml
      mainGenerator
          .getBasicMetadataGenerator()
          .addBasic(
              orig_id_mapping,
              getIdPersistentPropertyAuditingData(id_prop),
              id_prop.getValue(),
              null,
              true,
              true);
    }

    orig_id_mapping.addAttribute("name", mainGenerator.getVerEntCfg().getOriginalIdPropName());

    // Adding a relation to the revision entity (effectively: the "revision number" property)
    mainGenerator.addRevisionInfoRelation(orig_id_mapping);

    return new IdMappingData(mapper, orig_id_mapping, rel_id_mapping);
  }