Package org.hibernate.envers.configuration.internal.metadata

Source Code of org.hibernate.envers.configuration.internal.metadata.CollectionMetadataGenerator

/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, 2013, Red Hat Inc. 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 Inc.
*
* 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.internal.metadata;

import javax.persistence.JoinColumn;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.dom4j.Element;

import org.jboss.logging.Logger;

import org.hibernate.MappingException;
import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.envers.ModificationStore;
import org.hibernate.envers.RelationTargetAuditMode;
import org.hibernate.envers.configuration.internal.metadata.reader.AuditedPropertiesReader;
import org.hibernate.envers.configuration.internal.metadata.reader.ComponentAuditedPropertiesReader;
import org.hibernate.envers.configuration.internal.metadata.reader.ComponentAuditingData;
import org.hibernate.envers.configuration.internal.metadata.reader.PropertyAuditingData;
import org.hibernate.envers.internal.EnversMessageLogger;
import org.hibernate.envers.internal.entities.EntityConfiguration;
import org.hibernate.envers.internal.entities.IdMappingData;
import org.hibernate.envers.internal.entities.PropertyData;
import org.hibernate.envers.internal.entities.mapper.CompositeMapperBuilder;
import org.hibernate.envers.internal.entities.mapper.MultiPropertyMapper;
import org.hibernate.envers.internal.entities.mapper.PropertyMapper;
import org.hibernate.envers.internal.entities.mapper.SinglePropertyMapper;
import org.hibernate.envers.internal.entities.mapper.id.IdMapper;
import org.hibernate.envers.internal.entities.mapper.relation.BasicCollectionMapper;
import org.hibernate.envers.internal.entities.mapper.relation.CommonCollectionMapperData;
import org.hibernate.envers.internal.entities.mapper.relation.ListCollectionMapper;
import org.hibernate.envers.internal.entities.mapper.relation.MapCollectionMapper;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleComponentData;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
import org.hibernate.envers.internal.entities.mapper.relation.SortedMapCollectionMapper;
import org.hibernate.envers.internal.entities.mapper.relation.SortedSetCollectionMapper;
import org.hibernate.envers.internal.entities.mapper.relation.ToOneIdMapper;
import org.hibernate.envers.internal.entities.mapper.relation.component.MiddleDummyComponentMapper;
import org.hibernate.envers.internal.entities.mapper.relation.component.MiddleEmbeddableComponentMapper;
import org.hibernate.envers.internal.entities.mapper.relation.component.MiddleMapKeyIdComponentMapper;
import org.hibernate.envers.internal.entities.mapper.relation.component.MiddleMapKeyPropertyComponentMapper;
import org.hibernate.envers.internal.entities.mapper.relation.component.MiddleRelatedComponentMapper;
import org.hibernate.envers.internal.entities.mapper.relation.component.MiddleSimpleComponentMapper;
import org.hibernate.envers.internal.entities.mapper.relation.component.MiddleStraightComponentMapper;
import org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.ListProxy;
import org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.MapProxy;
import org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.SetProxy;
import org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.SortedMapProxy;
import org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.SortedSetProxy;
import org.hibernate.envers.internal.entities.mapper.relation.query.OneAuditEntityQueryGenerator;
import org.hibernate.envers.internal.entities.mapper.relation.query.RelationQueryGenerator;
import org.hibernate.envers.internal.tools.MappingTools;
import org.hibernate.envers.internal.tools.ReflectionTools;
import org.hibernate.envers.internal.tools.StringTools;
import org.hibernate.envers.internal.tools.Tools;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.IndexedCollection;
import org.hibernate.mapping.ManyToOne;
import org.hibernate.mapping.OneToMany;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.Value;
import org.hibernate.type.BagType;
import org.hibernate.type.ComponentType;
import org.hibernate.type.ListType;
import org.hibernate.type.ManyToOneType;
import org.hibernate.type.MapType;
import org.hibernate.type.SetType;
import org.hibernate.type.SortedMapType;
import org.hibernate.type.SortedSetType;
import org.hibernate.type.Type;

/**
* Generates metadata for a collection-valued property.
*
* @author Adam Warski (adam at warski dot org)
* @author HernпїЅn Chanfreau
*/
public final class CollectionMetadataGenerator {
  private 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 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() {
    final Type type = propertyValue.getType();
    final Value value = propertyValue.getElement();

    final boolean oneToManyAttachedType = type instanceof BagType || type instanceof SetType || type instanceof MapType || type instanceof ListType;
    final boolean inverseOneToMany = (value instanceof OneToMany) && (propertyValue.isInverse());
    final boolean owningManyToOneWithJoinTableBidirectional = (value instanceof ManyToOne) && (propertyAuditingData.getRelationMappedBy() != null);
    final boolean fakeOneToManyBidirectional = (value instanceof OneToMany) && (propertyAuditingData.getAuditMappedBy() != null);

    if ( oneToManyAttachedType && (inverseOneToMany || fakeOneToManyBidirectional || owningManyToOneWithJoinTableBidirectional) ) {
      // 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
    );

    final String mappedBy = getMappedBy( propertyValue );

    final IdMappingData referencedIdMapping = mainGenerator.getReferencedIdMappingData(
        referencingEntityName,
        referencedEntityName,
        propertyAuditingData,
        false
    );
    final IdMappingData referencingIdMapping = referencingEntityConfiguration.getIdMappingData();

    // Generating the id mappers data for the referencing side of the relation.
    final 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).
    final MiddleIdData referencedIdData = createMiddleIdData(
        referencedIdMapping,
        null, referencedEntityName
    );

    // Generating the element mapping.
    final 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.
    final RelationQueryGenerator queryGenerator = new OneAuditEntityQueryGenerator(
        mainGenerator.getGlobalCfg(),
        mainGenerator.getVerEntCfg(),
        mainGenerator.getAuditStrategy(),
        referencingIdData,
        referencedEntityName,
        referencedIdData,
        isEmbeddableElementType()
    );

    // Creating common mapper data.
    final 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).
      final String auditMappedBy = propertyAuditingData.getAuditMappedBy();

      // Creating a prefixed relation mapper.
      final 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 ) {
        final 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) {
    final 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 {
      final 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).
    final 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.
    final 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.
    final QueryGeneratorBuilder queryGeneratorBuilder = new QueryGeneratorBuilder(
        mainGenerator.getGlobalCfg(),
        mainGenerator.getVerEntCfg(),
        mainGenerator.getAuditStrategy(),
        referencingIdData,
        auditMiddleEntityName,
        isEmbeddableElementType()
    );

    // 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.
    // ******
    final MiddleComponentData elementComponentData = addValueToMiddleTable(
        propertyValue.getElement(),
        middleEntityXml,
        queryGeneratorBuilder,
        referencedPrefix,
        propertyAuditingData.getJoinTable().inverseJoinColumns()
    );

    // ******
    // Generating the index mapping, if an index exists.
    // ******
    final MiddleComponentData indexComponentData = addIndex( middleEntityXml, queryGeneratorBuilder );

    // ******
    // Generating the property mapper.
    // ******
    // Building the query generator.
    final RelationQueryGenerator queryGenerator = queryGeneratorBuilder.build( elementComponentData, indexComponentData );

    // Creating common data
    final 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 ) {
      final IndexedCollection indexedValue = (IndexedCollection) propertyValue;
      final 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 {
        final IdMappingData referencedIdMapping = mainGenerator.getEntitiesConfigurations()
            .get( referencedEntityName ).getIdMappingData();
        final 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) {
    final Type type = value.getType();
    if ( type instanceof ManyToOneType ) {
      final String prefixRelated = prefix + "_";

      final String referencedEntityName = MappingTools.getReferencedEntityName( value );

      final 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.
      final 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 if ( type instanceof ComponentType ) {
      // Collection of embeddable elements.
      final Component component = (Component) value;
      final Class componentClass = ReflectionTools.loadClass(
          component.getComponentClassName(),
          mainGenerator.getClassLoaderService()
      );
      final MiddleEmbeddableComponentMapper componentMapper = new MiddleEmbeddableComponentMapper(
          new MultiPropertyMapper(),
          componentClass
      );

      final Element parentXmlMapping = xmlMapping.getParent();
      final ComponentAuditingData auditData = new ComponentAuditingData();
      final ReflectionManager reflectionManager = mainGenerator.getCfg().getReflectionManager();

      new ComponentAuditedPropertiesReader(
          ModificationStore.FULL,
          new AuditedPropertiesReader.ComponentPropertiesSource( reflectionManager, component ),
          auditData, mainGenerator.getGlobalCfg(), reflectionManager, ""
      ).read();

      // Emulating first pass.
      for ( String auditedPropertyName : auditData.getPropertyNames() ) {
        final PropertyAuditingData nestedAuditingData = auditData.getPropertyAuditingData( auditedPropertyName );
        mainGenerator.addValue(
            parentXmlMapping,
            component.getProperty( auditedPropertyName ).getValue(),
            componentMapper,
            prefix, xmlMappingData,
            nestedAuditingData,
            true,
            true,
            true
        );
      }

      // Emulating second pass so that the relations can be mapped too.
      for ( String auditedPropertyName : auditData.getPropertyNames() ) {
        final PropertyAuditingData nestedAuditingData = auditData.getPropertyAuditingData( auditedPropertyName );
        mainGenerator.addValue(
            parentXmlMapping,
            component.getProperty( auditedPropertyName ).getValue(),
            componentMapper,
            referencingEntityName,
            xmlMappingData,
            nestedAuditingData,
            true,
            false,
            true
        );
      }

      // Add an additional column holding a number to make each entry unique within the set.
      // Embeddable properties may contain null values, so cannot be stored within composite primary key.
      if ( propertyValue.isSet() ) {
        final String setOrdinalPropertyName = mainGenerator.getVerEntCfg()
            .getEmbeddableSetOrdinalPropertyName();
        final Element ordinalProperty = MetadataTools.addProperty(
            xmlMapping, setOrdinalPropertyName, "integer", true, true
        );
        MetadataTools.addColumn(
            ordinalProperty, setOrdinalPropertyName, null, null, null, null, null, null, false
        );
      }

      return new MiddleComponentData( componentMapper, 0 );
    }
    else {
      // Last but one parameter: collection components are always insertable
      final 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) {
    final Type type = propertyValue.getType();
    final boolean embeddableElementType = isEmbeddableElementType();
    if ( type instanceof SortedSetType ) {
      currentMapper.addComposite(
          propertyAuditingData.getPropertyData(),
          new SortedSetCollectionMapper(
              commonCollectionMapperData,
              TreeSet.class,
              SortedSetProxy.class,
              elementComponentData,
              propertyValue.getComparator(),
              embeddableElementType,
              embeddableElementType
          )
      );
    }
    else if ( type instanceof SetType ) {
      currentMapper.addComposite(
          propertyAuditingData.getPropertyData(),
          new BasicCollectionMapper<Set>(
              commonCollectionMapperData,
              HashSet.class,
              SetProxy.class,
              elementComponentData,
              embeddableElementType,
              embeddableElementType
          )
      );
    }
    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(),
              embeddableElementType
          )
      );
    }
    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,
              embeddableElementType
          )
      );
    }
    else if ( type instanceof BagType ) {
      currentMapper.addComposite(
          propertyAuditingData.getPropertyData(),
          new BasicCollectionMapper<List>(
              commonCollectionMapperData,
              ArrayList.class,
              ListProxy.class,
              elementComponentData,
              embeddableElementType,
              embeddableElementType
          )
      );
    }
    else if ( type instanceof ListType ) {
      // Indexed collection, so <code>indexComponentData</code> is not null.
      currentMapper.addComposite(
          propertyAuditingData.getPropertyData(),
          new ListCollectionMapper(
              commonCollectionMapperData,
              elementComponentData,
              indexComponentData,
              embeddableElementType
          )
      );
    }
    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) {
    final String schema = mainGenerator.getSchema(
        propertyAuditingData.getJoinTable().schema(),
        propertyValue.getCollectionTable()
    );
    final String catalog = mainGenerator.getCatalog(
        propertyAuditingData.getJoinTable().catalog(),
        propertyValue.getCollectionTable()
    );

    final Element middleEntityXml = MetadataTools.createEntity(
        xmlMappingData.newAdditionalMapping(),
        new AuditTableData( auditMiddleEntityName, auditMiddleTableName, schema, catalog ), null, null
    );
    final 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(
        isEmbeddableElementType() ? middleEntityXmlId : middleEntityXml,
        middleEntityXml
    );

    // All other properties should also be part of the primary key of the middle entity.
    return middleEntityXmlId;
  }

  /**
   * Checks if the collection element is of {@link ComponentType} type.
   */
  private boolean isEmbeddableElementType() {
    return propertyValue.getElement().getType() instanceof ComponentType;
  }

  private String getMappedBy(Collection collectionValue) {
    PersistentClass referencedClass = null;
    if ( collectionValue.getElement() instanceof OneToMany ) {
      final 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.
      final ManyToOne manyToOneValue = (ManyToOne) collectionValue.getElement();
      referencedClass = manyToOneValue.getMappings().getClass( manyToOneValue.getReferencedEntityName() );
    }

    // If there's an @AuditMappedBy specified, returning it directly.
    final 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) {
    final Iterator<Property> assocClassProps = referencedClass.getPropertyIterator();
    while ( assocClassProps.hasNext() ) {
      final 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.
    final 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) {
    final Iterator<Property> properties = referencedClass.getPropertyIterator();
    while ( properties.hasNext() ) {
      final 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;
  }

}
TOP

Related Classes of org.hibernate.envers.configuration.internal.metadata.CollectionMetadataGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.