public Query getQuery(AuditReaderImplementor versionsReader, Object primaryKey, Number revision) {
    Query query = versionsReader.getSession().createQuery(queryString);
    query.setParameter("revision", revision);
    query.setParameter("delrevisiontype", RevisionType.DEL);
    for (QueryParameterData paramData :
        referencingIdData.getPrefixedMapper().mapToQueryParametersFromId(primaryKey)) {
      paramData.setParameterValue(query);
    }

    return query;
  }
  public ThreeEntityQueryGenerator(
      GlobalConfiguration globalCfg,
      AuditEntitiesConfiguration verEntCfg,
      AuditStrategy auditStrategy,
      String versionsMiddleEntityName,
      MiddleIdData referencingIdData,
      MiddleIdData referencedIdData,
      MiddleIdData indexIdData,
      boolean revisionTypeInId,
      MiddleComponentData... componentDatas) {
    this.referencingIdData = referencingIdData;

    /*
     * The query that we need to create:
     *   SELECT new list(ee, e, f) FROM versionsReferencedEntity e, versionsIndexEntity f, middleEntity ee
     *   WHERE
     * (entities referenced by the middle table; id_ref_ed = id of the referenced entity)
     *     ee.id_ref_ed = e.id_ref_ed AND
     * (entities referenced by the middle table; id_ref_ind = id of the index entity)
     *     ee.id_ref_ind = f.id_ref_ind AND
     * (only entities referenced by the association; id_ref_ing = id of the referencing entity)
     *     ee.id_ref_ing = :id_ref_ing AND
     * (selecting e entities at revision :revision)
     *   --> for DefaultAuditStrategy:
     *     e.revision = (SELECT max(e2.revision) FROM versionsReferencedEntity e2
     *       WHERE e2.revision <= :revision AND e2.id = e.id)
     *
     *   --> for ValidityAuditStrategy:
     *     e.revision <= :revision and (e.endRevision > :revision or e.endRevision is null)
     *
     *     AND
     *
     * (selecting f entities at revision :revision)
     *   --> for DefaultAuditStrategy:
     *     f.revision = (SELECT max(f2.revision) FROM versionsIndexEntity f2
     *       WHERE f2.revision <= :revision AND f2.id_ref_ed = f.id_ref_ed)
     *
     *   --> for ValidityAuditStrategy:
     *     f.revision <= :revision and (f.endRevision > :revision or f.endRevision is null)
     *
     *     AND
     *
     * (the association at revision :revision)
     *   --> for DefaultAuditStrategy:
     *     ee.revision = (SELECT max(ee2.revision) FROM middleEntity ee2
     *       WHERE ee2.revision <= :revision AND ee2.originalId.* = ee.originalId.*)
     *
     *   --> for ValidityAuditStrategy:
     *     ee.revision <= :revision and (ee.endRevision > :revision or ee.endRevision is null)
     *
    and (
        strtestent1_.REVEND>?
        or strtestent1_.REVEND is null
    )
    and (
        strtestent1_.REVEND>?
        or strtestent1_.REVEND is null
    )
    and (
        ternarymap0_.REVEND>?
        or ternarymap0_.REVEND is null
    )
     *
     *
     *
     * (only non-deleted entities and associations)
     *     ee.revision_type != DEL AND
     *     e.revision_type != DEL AND
     *     f.revision_type != DEL
     */
    String revisionPropertyPath = verEntCfg.getRevisionNumberPath();
    String originalIdPropertyName = verEntCfg.getOriginalIdPropName();
    String eeOriginalIdPropertyPath = "ee." + originalIdPropertyName;

    // SELECT new list(ee) FROM middleEntity ee
    QueryBuilder qb = new QueryBuilder(versionsMiddleEntityName, "ee");
    qb.addFrom(referencedIdData.getAuditEntityName(), "e");
    qb.addFrom(indexIdData.getAuditEntityName(), "f");
    qb.addProjection("new list", "ee, e, f", false, false);
    // WHERE
    Parameters rootParameters = qb.getRootParameters();
    // ee.id_ref_ed = e.id_ref_ed
    referencedIdData
        .getPrefixedMapper()
        .addIdsEqualToQuery(
            rootParameters,
            eeOriginalIdPropertyPath,
            referencedIdData.getOriginalMapper(),
            "e." + originalIdPropertyName);
    // ee.id_ref_ind = f.id_ref_ind
    indexIdData
        .getPrefixedMapper()
        .addIdsEqualToQuery(
            rootParameters,
            eeOriginalIdPropertyPath,
            indexIdData.getOriginalMapper(),
            "f." + originalIdPropertyName);
    // ee.originalId.id_ref_ing = :id_ref_ing
    referencingIdData
        .getPrefixedMapper()
        .addNamedIdEqualsToQuery(rootParameters, originalIdPropertyName, true);

    // (selecting e entities at revision :revision)
    // --> based on auditStrategy (see above)
    auditStrategy.addEntityAtRevisionRestriction(
        globalCfg,
        qb,
        "e." + revisionPropertyPath,
        "e." + verEntCfg.getRevisionEndFieldName(),
        false,
        referencedIdData,
        revisionPropertyPath,
        originalIdPropertyName,
        "e",
        "e2");

    // (selecting f entities at revision :revision)
    // --> based on auditStrategy (see above)
    auditStrategy.addEntityAtRevisionRestriction(
        globalCfg,
        qb,
        "e." + revisionPropertyPath,
        "e." + verEntCfg.getRevisionEndFieldName(),
        false,
        referencedIdData,
        revisionPropertyPath,
        originalIdPropertyName,
        "f",
        "f2");

    // (with ee association at revision :revision)
    // --> based on auditStrategy (see above)
    auditStrategy.addAssociationAtRevisionRestriction(
        qb,
        revisionPropertyPath,
        verEntCfg.getRevisionEndFieldName(),
        true,
        referencingIdData,
        versionsMiddleEntityName,
        eeOriginalIdPropertyPath,
        revisionPropertyPath,
        originalIdPropertyName,
        "ee",
        componentDatas);

    // ee.revision_type != DEL
    final String revisionTypePropName =
        (revisionTypeInId
            ? verEntCfg.getOriginalIdPropName() + '.' + verEntCfg.getRevisionTypePropName()
            : verEntCfg.getRevisionTypePropName());
    rootParameters.addWhereWithNamedParam(revisionTypePropName, "!=", "delrevisiontype");
    // e.revision_type != DEL
    rootParameters.addWhereWithNamedParam(
        "e." + revisionTypePropName, false, "!=", "delrevisiontype");
    // f.revision_type != DEL
    rootParameters.addWhereWithNamedParam(
        "f." + revisionTypePropName, false, "!=", "delrevisiontype");

    StringBuilder sb = new StringBuilder();
    qb.build(sb, Collections.<String, Object>emptyMap());
    queryString = sb.toString();
  }
  public TwoEntityOneAuditedQueryGenerator(
      AuditEntitiesConfiguration verEntCfg,
      AuditStrategy auditStrategy,
      String versionsMiddleEntityName,
      MiddleIdData referencingIdData,
      MiddleIdData referencedIdData,
      MiddleComponentData... componentDatas) {
    this.referencingIdData = referencingIdData;

    /*
     * The query that we need to create:
     *   SELECT new list(ee, e) FROM referencedEntity e, middleEntity ee
     *   WHERE
     * (entities referenced by the middle table; id_ref_ed = id of the referenced entity)
     *     ee.id_ref_ed = e.id_ref_ed AND
     * (only entities referenced by the association; id_ref_ing = id of the referencing entity)
     *     ee.id_ref_ing = :id_ref_ing AND
     *
     * (the association at revision :revision)
     *   --> for DefaultAuditStrategy:
     *     ee.revision = (SELECT max(ee2.revision) FROM middleEntity ee2
     *       WHERE ee2.revision <= :revision AND ee2.originalId.* = ee.originalId.*)
     *
     *   --> for ValidTimeAuditStrategy:
     *     ee.revision <= :revision and (ee.endRevision > :revision or ee.endRevision is null)
     *
     *     AND
     *
     * (only non-deleted entities and associations)
     *     ee.revision_type != DEL
     */
    String revisionPropertyPath = verEntCfg.getRevisionNumberPath();
    String originalIdPropertyName = verEntCfg.getOriginalIdPropName();

    String eeOriginalIdPropertyPath = "ee." + originalIdPropertyName;

    // SELECT new list(ee) FROM middleEntity ee
    QueryBuilder qb = new QueryBuilder(versionsMiddleEntityName, "ee");
    qb.addFrom(referencedIdData.getEntityName(), "e");
    qb.addProjection("new list", "ee, e", false, false);
    // WHERE
    Parameters rootParameters = qb.getRootParameters();
    // ee.id_ref_ed = e.id_ref_ed
    referencedIdData
        .getPrefixedMapper()
        .addIdsEqualToQuery(
            rootParameters, eeOriginalIdPropertyPath, referencedIdData.getOriginalMapper(), "e");
    // ee.originalId.id_ref_ing = :id_ref_ing
    referencingIdData
        .getPrefixedMapper()
        .addNamedIdEqualsToQuery(rootParameters, originalIdPropertyName, true);

    // (with ee association at revision :revision)
    // --> based on auditStrategy (see above)
    auditStrategy.addAssociationAtRevisionRestriction(
        qb,
        revisionPropertyPath,
        verEntCfg.getRevisionEndFieldName(),
        true,
        referencingIdData,
        versionsMiddleEntityName,
        eeOriginalIdPropertyPath,
        revisionPropertyPath,
        originalIdPropertyName,
        componentDatas);

    // ee.revision_type != DEL
    rootParameters.addWhereWithNamedParam(
        verEntCfg.getRevisionTypePropName(), "!=", "delrevisiontype");

    StringBuilder sb = new StringBuilder();
    qb.build(sb, Collections.<String, Object>emptyMap());
    queryString = sb.toString();
  }
  @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);
  }