Package org.hibernate.persister.entity

Source Code of org.hibernate.persister.entity.JoinedSubclassEntityPersister

/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008-2011, 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.persister.entity;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.cache.spi.access.EntityRegionAccessStrategy;
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
import org.hibernate.engine.OptimisticLockStyle;
import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.DynamicFilterAliasGenerator;
import org.hibernate.internal.FilterAliasGenerator;
import org.hibernate.internal.util.MarkerObject;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Formula;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.MappedSuperclass;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.Subclass;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.binding.EntityBinding;
import org.hibernate.sql.CaseFragment;
import org.hibernate.sql.InFragment;
import org.hibernate.sql.Insert;
import org.hibernate.sql.SelectFragment;
import org.hibernate.type.*;
import org.hibernate.type.DiscriminatorType;

import org.jboss.logging.Logger;

/**
* An <tt>EntityPersister</tt> implementing the normalized "table-per-subclass"
* mapping strategy
*
* @author Gavin King
*/
public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
  private static final Logger log = Logger.getLogger( JoinedSubclassEntityPersister.class );

  private static final String IMPLICIT_DISCRIMINATOR_ALIAS = "clazz_";
  private static final Object NULL_DISCRIMINATOR = new MarkerObject("<null discriminator>");
  private static final Object NOT_NULL_DISCRIMINATOR = new MarkerObject("<not null discriminator>");
  private static final String NULL_STRING = "null";
  private static final String NOT_NULL_STRING = "not null";

  // the class hierarchy structure
  private final int tableSpan;
  private final String[] tableNames;
  private final String[] naturalOrderTableNames;
  private final String[][] tableKeyColumns;
  private final String[][] tableKeyColumnReaders;
  private final String[][] tableKeyColumnReaderTemplates;
  private final String[][] naturalOrderTableKeyColumns;
  private final String[][] naturalOrderTableKeyColumnReaders;
  private final String[][] naturalOrderTableKeyColumnReaderTemplates;
  private final boolean[] naturalOrderCascadeDeleteEnabled;

  private final String[] spaces;

  private final String[] subclassClosure;

  private final String[] subclassTableNameClosure;
  private final String[][] subclassTableKeyColumnClosure;
  private final boolean[] isClassOrSuperclassTable;

  // properties of this class, including inherited properties
  private final int[] naturalOrderPropertyTableNumbers;
  private final int[] propertyTableNumbers;

  // the closure of all properties in the entire hierarchy including
  // subclasses and superclasses of this class
  private final int[] subclassPropertyTableNumberClosure;

  // the closure of all columns used by the entire hierarchy including
  // subclasses and superclasses of this class
  private final int[] subclassColumnTableNumberClosure;
  private final int[] subclassFormulaTableNumberClosure;

  private final boolean[] subclassTableSequentialSelect;
  private final boolean[] subclassTableIsLazyClosure;

  // subclass discrimination works by assigning particular
  // values to certain combinations of null primary key
  // values in the outer join using an SQL CASE
  private final Map subclassesByDiscriminatorValue = new HashMap();
  private final String[] discriminatorValues;
  private final String[] notNullColumnNames;
  private final int[] notNullColumnTableNumbers;

  private final String[] constraintOrderedTableNames;
  private final String[][] constraintOrderedKeyColumnNames;

  private final Object discriminatorValue;
  private final String discriminatorSQLString;
  private final DiscriminatorType discriminatorType;
  private final String explicitDiscriminatorColumnName;
  private final String discriminatorAlias;

  // Span of the tables directly mapped by this entity and super-classes, if any
  private final int coreTableSpan;
  // only contains values for SecondaryTables, ie. not tables part of the "coreTableSpan"
  private final boolean[] isNullableTable;

  //INITIALIZATION:

  public JoinedSubclassEntityPersister(
      final PersistentClass persistentClass,
      final EntityRegionAccessStrategy cacheAccessStrategy,
      final NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy,
      final SessionFactoryImplementor factory,
      final Mapping mapping) throws HibernateException {

    super( persistentClass, cacheAccessStrategy, naturalIdRegionAccessStrategy, factory );

    // DISCRIMINATOR

    if ( persistentClass.isPolymorphic() ) {
      final Value discriminatorMapping = persistentClass.getDiscriminator();
      if ( discriminatorMapping != null ) {
        log.debug( "Encountered explicit discriminator mapping for joined inheritance" );

        final Selectable selectable = discriminatorMapping.getColumnIterator().next();
        if ( Formula.class.isInstance( selectable ) ) {
          throw new MappingException( "Discriminator formulas on joined inheritance hierarchies not supported at this time" );
        }
        else {
          final Column column = (Column) selectable;
          explicitDiscriminatorColumnName = column.getQuotedName( factory.getDialect() );
          discriminatorAlias = column.getAlias( factory.getDialect(), persistentClass.getRootTable() );
        }
        discriminatorType = (DiscriminatorType) persistentClass.getDiscriminator().getType();
        if ( persistentClass.isDiscriminatorValueNull() ) {
          discriminatorValue = NULL_DISCRIMINATOR;
          discriminatorSQLString = InFragment.NULL;
        }
        else if ( persistentClass.isDiscriminatorValueNotNull() ) {
          discriminatorValue = NOT_NULL_DISCRIMINATOR;
          discriminatorSQLString = InFragment.NOT_NULL;
        }
        else {
          try {
            discriminatorValue = discriminatorType.stringToObject( persistentClass.getDiscriminatorValue() );
            discriminatorSQLString = discriminatorType.objectToSQLString( discriminatorValue, factory.getDialect() );
          }
          catch (ClassCastException cce) {
            throw new MappingException("Illegal discriminator type: " + discriminatorType.getName() );
          }
          catch (Exception e) {
            throw new MappingException("Could not format discriminator value to SQL string", e);
          }
        }
      }
      else {
        explicitDiscriminatorColumnName = null;
        discriminatorAlias = IMPLICIT_DISCRIMINATOR_ALIAS;
        discriminatorType = StandardBasicTypes.INTEGER;
        try {
          discriminatorValue = persistentClass.getSubclassId();
          discriminatorSQLString = discriminatorValue.toString();
        }
        catch ( Exception e ) {
          throw new MappingException( "Could not format discriminator value to SQL string", e );
        }
      }
    }
    else {
      explicitDiscriminatorColumnName = null;
      discriminatorAlias = IMPLICIT_DISCRIMINATOR_ALIAS;
      discriminatorType = StandardBasicTypes.INTEGER;
      discriminatorValue = null;
      discriminatorSQLString = null;
    }

    if ( optimisticLockStyle() == OptimisticLockStyle.ALL || optimisticLockStyle() == OptimisticLockStyle.DIRTY ) {
      throw new MappingException( "optimistic-lock=all|dirty not supported for joined-subclass mappings [" + getEntityName() + "]" );
    }

    //MULTITABLES

    final int idColumnSpan = getIdentifierColumnSpan();

    ArrayList tables = new ArrayList();
    ArrayList keyColumns = new ArrayList();
    ArrayList keyColumnReaders = new ArrayList();
    ArrayList keyColumnReaderTemplates = new ArrayList();
    ArrayList cascadeDeletes = new ArrayList();
    Iterator titer = persistentClass.getTableClosureIterator();
    Iterator kiter = persistentClass.getKeyClosureIterator();
    while ( titer.hasNext() ) {
      Table tab = (Table) titer.next();
      KeyValue key = (KeyValue) kiter.next();
      String tabname = tab.getQualifiedName(
          factory.getDialect(),
          factory.getSettings().getDefaultCatalogName(),
          factory.getSettings().getDefaultSchemaName()
      );
      tables.add( tabname );
      String[] keyCols = new String[idColumnSpan];
      String[] keyColReaders = new String[idColumnSpan];
      String[] keyColReaderTemplates = new String[idColumnSpan];
      Iterator citer = key.getColumnIterator();
      for ( int k = 0; k < idColumnSpan; k++ ) {
        Column column = (Column) citer.next();
        keyCols[k] = column.getQuotedName( factory.getDialect() );
        keyColReaders[k] = column.getReadExpr( factory.getDialect() );
        keyColReaderTemplates[k] = column.getTemplate( factory.getDialect(), factory.getSqlFunctionRegistry() );
      }
      keyColumns.add( keyCols );
      keyColumnReaders.add( keyColReaders );
      keyColumnReaderTemplates.add( keyColReaderTemplates );
      cascadeDeletes.add( key.isCascadeDeleteEnabled() && factory.getDialect().supportsCascadeDelete() );
    }

    //Span of the tables directly mapped by this entity and super-classes, if any
    coreTableSpan = tables.size();

    isNullableTable = new boolean[persistentClass.getJoinClosureSpan()];

    int tableIndex = 0;
    Iterator joinIter = persistentClass.getJoinClosureIterator();
    while ( joinIter.hasNext() ) {
      Join join = (Join) joinIter.next();

      isNullableTable[tableIndex++] = join.isOptional();

      Table table = join.getTable();

      String tableName = table.getQualifiedName(
          factory.getDialect(),
          factory.getSettings().getDefaultCatalogName(),
          factory.getSettings().getDefaultSchemaName()
      );
      tables.add( tableName );

      KeyValue key = join.getKey();
      int joinIdColumnSpan = key.getColumnSpan();

      String[] keyCols = new String[joinIdColumnSpan];
      String[] keyColReaders = new String[joinIdColumnSpan];
      String[] keyColReaderTemplates = new String[joinIdColumnSpan];

      Iterator citer = key.getColumnIterator();

      for ( int k = 0; k < joinIdColumnSpan; k++ ) {
        Column column = (Column) citer.next();
        keyCols[k] = column.getQuotedName( factory.getDialect() );
        keyColReaders[k] = column.getReadExpr( factory.getDialect() );
        keyColReaderTemplates[k] = column.getTemplate( factory.getDialect(), factory.getSqlFunctionRegistry() );
      }
      keyColumns.add( keyCols );
      keyColumnReaders.add( keyColReaders );
      keyColumnReaderTemplates.add( keyColReaderTemplates );
      cascadeDeletes.add( key.isCascadeDeleteEnabled() && factory.getDialect().supportsCascadeDelete() );
    }

    naturalOrderTableNames = ArrayHelper.toStringArray( tables );
    naturalOrderTableKeyColumns = ArrayHelper.to2DStringArray( keyColumns );
    naturalOrderTableKeyColumnReaders = ArrayHelper.to2DStringArray( keyColumnReaders );
    naturalOrderTableKeyColumnReaderTemplates = ArrayHelper.to2DStringArray( keyColumnReaderTemplates );
    naturalOrderCascadeDeleteEnabled = ArrayHelper.toBooleanArray( cascadeDeletes );

    ArrayList subtables = new ArrayList();
    ArrayList isConcretes = new ArrayList();
    ArrayList isDeferreds = new ArrayList();
    ArrayList isLazies = new ArrayList();

    keyColumns = new ArrayList();
    titer = persistentClass.getSubclassTableClosureIterator();
    while ( titer.hasNext() ) {
      Table tab = (Table) titer.next();
      isConcretes.add( persistentClass.isClassOrSuperclassTable( tab ) );
      isDeferreds.add( Boolean.FALSE );
      isLazies.add( Boolean.FALSE );
      String tabname = tab.getQualifiedName(
          factory.getDialect(),
          factory.getSettings().getDefaultCatalogName(),
          factory.getSettings().getDefaultSchemaName()
      );
      subtables.add( tabname );
      String[] key = new String[idColumnSpan];
      Iterator citer = tab.getPrimaryKey().getColumnIterator();
      for ( int k = 0; k < idColumnSpan; k++ ) {
        key[k] = ( (Column) citer.next() ).getQuotedName( factory.getDialect() );
      }
      keyColumns.add( key );
    }

    //Add joins
    joinIter = persistentClass.getSubclassJoinClosureIterator();
    while ( joinIter.hasNext() ) {
      Join join = (Join) joinIter.next();

      Table tab = join.getTable();

      isConcretes.add( persistentClass.isClassOrSuperclassTable( tab ) );
      isDeferreds.add( join.isSequentialSelect() );
      isLazies.add( join.isLazy() );

      String tabname = tab.getQualifiedName(
          factory.getDialect(),
          factory.getSettings().getDefaultCatalogName(),
          factory.getSettings().getDefaultSchemaName()
      );
      subtables.add( tabname );
      String[] key = new String[idColumnSpan];
      Iterator citer = tab.getPrimaryKey().getColumnIterator();
      for ( int k = 0; k < idColumnSpan; k++ ) {
        key[k] = ( (Column) citer.next() ).getQuotedName( factory.getDialect() );
      }
      keyColumns.add( key );
    }

    String[] naturalOrderSubclassTableNameClosure = ArrayHelper.toStringArray( subtables );
    String[][] naturalOrderSubclassTableKeyColumnClosure = ArrayHelper.to2DStringArray( keyColumns );
    isClassOrSuperclassTable = ArrayHelper.toBooleanArray( isConcretes );
    subclassTableSequentialSelect = ArrayHelper.toBooleanArray( isDeferreds );
    subclassTableIsLazyClosure = ArrayHelper.toBooleanArray( isLazies );

    constraintOrderedTableNames = new String[naturalOrderSubclassTableNameClosure.length];
    constraintOrderedKeyColumnNames = new String[naturalOrderSubclassTableNameClosure.length][];
    int currentPosition = 0;
    for ( int i = naturalOrderSubclassTableNameClosure.length - 1; i >= 0; i--, currentPosition++ ) {
      constraintOrderedTableNames[currentPosition] = naturalOrderSubclassTableNameClosure[i];
      constraintOrderedKeyColumnNames[currentPosition] = naturalOrderSubclassTableKeyColumnClosure[i];
    }

    /**
     * Suppose an entity Client extends Person, mapped to the tables CLIENT and PERSON respectively.
     * For the Client entity:
     * naturalOrderTableNames -> PERSON, CLIENT; this reflects the sequence in which the tables are
     * added to the meta-data when the annotated entities are processed.
     * However, in some instances, for example when generating joins, the CLIENT table needs to be
     * the first table as it will the driving table.
     * tableNames -> CLIENT, PERSON
     */

    tableSpan = naturalOrderTableNames.length;
    tableNames = reverse( naturalOrderTableNames, coreTableSpan );
    tableKeyColumns = reverse( naturalOrderTableKeyColumns, coreTableSpan );
    tableKeyColumnReaders = reverse( naturalOrderTableKeyColumnReaders, coreTableSpan );
    tableKeyColumnReaderTemplates = reverse( naturalOrderTableKeyColumnReaderTemplates, coreTableSpan );
    subclassTableNameClosure = reverse( naturalOrderSubclassTableNameClosure, coreTableSpan );
    subclassTableKeyColumnClosure = reverse( naturalOrderSubclassTableKeyColumnClosure, coreTableSpan );

    spaces = ArrayHelper.join(
        tableNames,
        ArrayHelper.toStringArray( persistentClass.getSynchronizedTables() )
    );

    // Custom sql
    customSQLInsert = new String[tableSpan];
    customSQLUpdate = new String[tableSpan];
    customSQLDelete = new String[tableSpan];
    insertCallable = new boolean[tableSpan];
    updateCallable = new boolean[tableSpan];
    deleteCallable = new boolean[tableSpan];
    insertResultCheckStyles = new ExecuteUpdateResultCheckStyle[tableSpan];
    updateResultCheckStyles = new ExecuteUpdateResultCheckStyle[tableSpan];
    deleteResultCheckStyles = new ExecuteUpdateResultCheckStyle[tableSpan];

    PersistentClass pc = persistentClass;
    int jk = coreTableSpan - 1;
    while ( pc != null ) {
      customSQLInsert[jk] = pc.getCustomSQLInsert();
      insertCallable[jk] = customSQLInsert[jk] != null && pc.isCustomInsertCallable();
      insertResultCheckStyles[jk] = pc.getCustomSQLInsertCheckStyle() == null
          ? ExecuteUpdateResultCheckStyle.determineDefault(
          customSQLInsert[jk], insertCallable[jk]
      )
          : pc.getCustomSQLInsertCheckStyle();
      customSQLUpdate[jk] = pc.getCustomSQLUpdate();
      updateCallable[jk] = customSQLUpdate[jk] != null && pc.isCustomUpdateCallable();
      updateResultCheckStyles[jk] = pc.getCustomSQLUpdateCheckStyle() == null
          ? ExecuteUpdateResultCheckStyle.determineDefault( customSQLUpdate[jk], updateCallable[jk] )
          : pc.getCustomSQLUpdateCheckStyle();
      customSQLDelete[jk] = pc.getCustomSQLDelete();
      deleteCallable[jk] = customSQLDelete[jk] != null && pc.isCustomDeleteCallable();
      deleteResultCheckStyles[jk] = pc.getCustomSQLDeleteCheckStyle() == null
          ? ExecuteUpdateResultCheckStyle.determineDefault( customSQLDelete[jk], deleteCallable[jk] )
          : pc.getCustomSQLDeleteCheckStyle();
      jk--;
      pc = pc.getSuperclass();
    }

    if ( jk != -1 ) {
      throw new AssertionFailure( "Tablespan does not match height of joined-subclass hiearchy." );
    }

    joinIter = persistentClass.getJoinClosureIterator();
    int j = coreTableSpan;
    while ( joinIter.hasNext() ) {
      Join join = (Join) joinIter.next();

      customSQLInsert[j] = join.getCustomSQLInsert();
      insertCallable[j] = customSQLInsert[j] != null && join.isCustomInsertCallable();
      insertResultCheckStyles[j] = join.getCustomSQLInsertCheckStyle() == null
          ? ExecuteUpdateResultCheckStyle.determineDefault( customSQLInsert[j], insertCallable[j] )
          : join.getCustomSQLInsertCheckStyle();
      customSQLUpdate[j] = join.getCustomSQLUpdate();
      updateCallable[j] = customSQLUpdate[j] != null && join.isCustomUpdateCallable();
      updateResultCheckStyles[j] = join.getCustomSQLUpdateCheckStyle() == null
          ? ExecuteUpdateResultCheckStyle.determineDefault( customSQLUpdate[j], updateCallable[j] )
          : join.getCustomSQLUpdateCheckStyle();
      customSQLDelete[j] = join.getCustomSQLDelete();
      deleteCallable[j] = customSQLDelete[j] != null && join.isCustomDeleteCallable();
      deleteResultCheckStyles[j] = join.getCustomSQLDeleteCheckStyle() == null
          ? ExecuteUpdateResultCheckStyle.determineDefault( customSQLDelete[j], deleteCallable[j] )
          : join.getCustomSQLDeleteCheckStyle();
      j++;
    }

    // PROPERTIES
    int hydrateSpan = getPropertySpan();
    naturalOrderPropertyTableNumbers = new int[hydrateSpan];
    propertyTableNumbers = new int[hydrateSpan];
    Iterator iter = persistentClass.getPropertyClosureIterator();
    int i = 0;
    while ( iter.hasNext() ) {
      Property prop = (Property) iter.next();
      String tabname = prop.getValue().getTable().getQualifiedName(
          factory.getDialect(),
          factory.getSettings().getDefaultCatalogName(),
          factory.getSettings().getDefaultSchemaName()
      );
      propertyTableNumbers[i] = getTableId( tabname, tableNames );
      naturalOrderPropertyTableNumbers[i] = getTableId( tabname, naturalOrderTableNames );
      i++;
    }

    // subclass closure properties

    //TODO: code duplication with SingleTableEntityPersister

    ArrayList columnTableNumbers = new ArrayList();
    ArrayList formulaTableNumbers = new ArrayList();
    ArrayList propTableNumbers = new ArrayList();

    iter = persistentClass.getSubclassPropertyClosureIterator();
    while ( iter.hasNext() ) {
      Property prop = (Property) iter.next();
      Table tab = prop.getValue().getTable();
      String tabname = tab.getQualifiedName(
          factory.getDialect(),
          factory.getSettings().getDefaultCatalogName(),
          factory.getSettings().getDefaultSchemaName()
      );
      Integer tabnum = getTableId( tabname, subclassTableNameClosure );
      propTableNumbers.add( tabnum );

      Iterator citer = prop.getColumnIterator();
      while ( citer.hasNext() ) {
        Selectable thing = (Selectable) citer.next();
        if ( thing.isFormula() ) {
          formulaTableNumbers.add( tabnum );
        }
        else {
          columnTableNumbers.add( tabnum );
        }
      }

    }

    subclassColumnTableNumberClosure = ArrayHelper.toIntArray( columnTableNumbers );
    subclassPropertyTableNumberClosure = ArrayHelper.toIntArray( propTableNumbers );
    subclassFormulaTableNumberClosure = ArrayHelper.toIntArray( formulaTableNumbers );

    // SUBCLASSES

    int subclassSpan = persistentClass.getSubclassSpan() + 1;
    subclassClosure = new String[subclassSpan];
    subclassClosure[subclassSpan - 1] = getEntityName();
    if ( persistentClass.isPolymorphic() ) {
      subclassesByDiscriminatorValue.put( discriminatorValue, getEntityName() );
      discriminatorValues = new String[subclassSpan];
      discriminatorValues[subclassSpan - 1] = discriminatorSQLString;
      notNullColumnTableNumbers = new int[subclassSpan];
      final int id = getTableId(
          persistentClass.getTable().getQualifiedName(
              factory.getDialect(),
              factory.getSettings().getDefaultCatalogName(),
              factory.getSettings().getDefaultSchemaName()
          ),
          subclassTableNameClosure
      );
      notNullColumnTableNumbers[subclassSpan - 1] = id;
      notNullColumnNames = new String[subclassSpan];
      notNullColumnNames[subclassSpan - 1] = subclassTableKeyColumnClosure[id][0]; //( (Column) model.getTable().getPrimaryKey().getColumnIterator().next() ).getName();
    }
    else {
      discriminatorValues = null;
      notNullColumnTableNumbers = null;
      notNullColumnNames = null;
    }

    iter = persistentClass.getSubclassIterator();
    int k = 0;
    while ( iter.hasNext() ) {
      Subclass sc = (Subclass) iter.next();
      subclassClosure[k] = sc.getEntityName();
      try {
        if ( persistentClass.isPolymorphic() ) {
          final Object discriminatorValue;
          if ( explicitDiscriminatorColumnName != null ) {
            if ( sc.isDiscriminatorValueNull() ) {
              discriminatorValue = NULL_DISCRIMINATOR;
            }
            else if ( sc.isDiscriminatorValueNotNull() ) {
              discriminatorValue = NOT_NULL_DISCRIMINATOR;
            }
            else {
              try {
                discriminatorValue = discriminatorType.stringToObject( sc.getDiscriminatorValue() );
              }
              catch (ClassCastException cce) {
                throw new MappingException( "Illegal discriminator type: " + discriminatorType.getName() );
              }
              catch (Exception e) {
                throw new MappingException( "Could not format discriminator value to SQL string", e);
              }
            }
          }
          else {
            // we now use subclass ids that are consistent across all
            // persisters for a class hierarchy, so that the use of
            // "foo.class = Bar" works in HQL
            discriminatorValue = sc.getSubclassId();
          }

          subclassesByDiscriminatorValue.put( discriminatorValue, sc.getEntityName() );
          discriminatorValues[k] = discriminatorValue.toString();
          int id = getTableId(
              sc.getTable().getQualifiedName(
                  factory.getDialect(),
                  factory.getSettings().getDefaultCatalogName(),
                  factory.getSettings().getDefaultSchemaName()
              ),
              subclassTableNameClosure
          );
          notNullColumnTableNumbers[k] = id;
          notNullColumnNames[k] = subclassTableKeyColumnClosure[id][0]; //( (Column) sc.getTable().getPrimaryKey().getColumnIterator().next() ).getName();
        }
      }
      catch ( Exception e ) {
        throw new MappingException( "Error parsing discriminator value", e );
      }
      k++;
    }

    subclassNamesBySubclassTable = buildSubclassNamesBySubclassTableMapping( persistentClass, factory );

    initLockers();

    initSubclassPropertyAliasesMap( persistentClass );

    postConstruct( mapping );

  }


  /**
   * Used to hold the name of subclasses that each "subclass table" is part of.  For example, given a hierarchy like:
   * {@code JoinedEntity <- JoinedEntitySubclass <- JoinedEntitySubSubclass}..
   * <p/>
   * For the persister for JoinedEntity, we'd have:
   * <pre>
   *     subclassClosure[0] = "JoinedEntitySubSubclass"
   *     subclassClosure[1] = "JoinedEntitySubclass"
   *     subclassClosure[2] = "JoinedEntity"
   *
   *     subclassTableNameClosure[0] = "T_JoinedEntity"
   *     subclassTableNameClosure[1] = "T_JoinedEntitySubclass"
   *     subclassTableNameClosure[2] = "T_JoinedEntitySubSubclass"
   *
   *     subclassNameClosureBySubclassTable[0] = ["JoinedEntitySubSubclass", "JoinedEntitySubclass"]
   *     subclassNameClosureBySubclassTable[1] = ["JoinedEntitySubSubclass"]
   * </pre>
   * Note that there are only 2 entries in subclassNameClosureBySubclassTable.  That is because there are really only
   * 2 tables here that make up the subclass mapping, the others make up the class/superclass table mappings.  We
   * do not need to account for those here.  The "offset" is defined by the value of {@link #getTableSpan()}.
   * Therefore the corresponding row in subclassNameClosureBySubclassTable for a given row in subclassTableNameClosure
   * is calculated as {@code subclassTableNameClosureIndex - getTableSpan()}.
   * <p/>
   * As we consider each subclass table we can look into this array based on the subclass table's index and see
   * which subclasses would require it to be included.  E.g., given {@code TREAT( x AS JoinedEntitySubSubclass )},
   * when trying to decide whether to include join to "T_JoinedEntitySubclass" (subclassTableNameClosureIndex = 1),
   * we'd look at {@code subclassNameClosureBySubclassTable[0]} and see if the TREAT-AS subclass name is included in
   * its values.  Since {@code subclassNameClosureBySubclassTable[1]} includes "JoinedEntitySubSubclass", we'd
   * consider it included.
   * <p/>
   * {@link #subclassTableNameClosure} also accounts for secondary tables and we properly handle those as we
   * build the subclassNamesBySubclassTable array and they are therefore properly handled when we use it
   */
  private final String[][] subclassNamesBySubclassTable;

  /**
   * Essentially we are building a mapping that we can later use to determine whether a given "subclass table"
   * should be included in joins when JPA TREAT-AS is used.
   *
   * @param persistentClass
   * @param factory
   * @return
   */
  private String[][] buildSubclassNamesBySubclassTableMapping(PersistentClass persistentClass, SessionFactoryImplementor factory) {
    // this value represents the number of subclasses (and not the class itself)
    final int numberOfSubclassTables = subclassTableNameClosure.length - coreTableSpan;
    if ( numberOfSubclassTables == 0 ) {
      return new String[0][];
    }

    final String[][] mapping = new String[numberOfSubclassTables][];
    processPersistentClassHierarchy( persistentClass, true, factory, mapping );
    return mapping;
  }

  private Set<String> processPersistentClassHierarchy(
      PersistentClass persistentClass,
      boolean isBase,
      SessionFactoryImplementor factory,
      String[][] mapping) {

    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // collect all the class names that indicate that the "main table" of the given PersistentClass should be
    // included when one of the collected class names is used in TREAT
    final Set<String> classNames = new HashSet<String>();

    final Iterator itr = persistentClass.getDirectSubclasses();
    while ( itr.hasNext() ) {
      final Subclass subclass = (Subclass) itr.next();
      final Set<String> subclassSubclassNames = processPersistentClassHierarchy(
          subclass,
          false,
          factory,
          mapping
      );
      classNames.addAll( subclassSubclassNames );
    }

    classNames.add( persistentClass.getEntityName() );

    if ( ! isBase ) {
      MappedSuperclass msc = persistentClass.getSuperMappedSuperclass();
      while ( msc != null ) {
        classNames.add( msc.getMappedClass().getName() );
        msc = msc.getSuperMappedSuperclass();
      }

      associateSubclassNamesToSubclassTableIndexes( persistentClass, classNames, mapping, factory );
    }

    return classNames;
  }

  private void associateSubclassNamesToSubclassTableIndexes(
      PersistentClass persistentClass,
      Set<String> classNames,
      String[][] mapping,
      SessionFactoryImplementor factory) {

    final String tableName = persistentClass.getTable().getQualifiedName(
        factory.getDialect(),
        factory.getSettings().getDefaultCatalogName(),
        factory.getSettings().getDefaultSchemaName()
    );

    associateSubclassNamesToSubclassTableIndex( tableName, classNames, mapping );

    Iterator itr = persistentClass.getJoinIterator();
    while ( itr.hasNext() ) {
      final Join join = (Join) itr.next();
      final String secondaryTableName = join.getTable().getQualifiedName(
          factory.getDialect(),
          factory.getSettings().getDefaultCatalogName(),
          factory.getSettings().getDefaultSchemaName()
      );
      associateSubclassNamesToSubclassTableIndex( secondaryTableName, classNames, mapping );
    }
  }

  private void associateSubclassNamesToSubclassTableIndex(
      String tableName,
      Set<String> classNames,
      String[][] mapping) {
    // find the table's entry in the subclassTableNameClosure array
    boolean found = false;
    for ( int i = 0; i < subclassTableNameClosure.length; i++ ) {
      if ( subclassTableNameClosure[i].equals( tableName ) ) {
        found = true;
        final int index = i - coreTableSpan;
        if ( index < 0 || index >= mapping.length ) {
          throw new IllegalStateException(
              String.format(
                  "Encountered 'subclass table index' [%s] was outside expected range ( [%s] < i < [%s] )",
                  index,
                  0,
                  mapping.length
              )
          );
        }
        mapping[index] = classNames.toArray( new String[ classNames.size() ] );
        break;
      }
    }
    if ( !found ) {
      throw new IllegalStateException(
          String.format(
              "Was unable to locate subclass table [%s] in 'subclassTableNameClosure'",
              tableName
          )
      );
    }
  }

  public JoinedSubclassEntityPersister(
      final EntityBinding entityBinding,
      final EntityRegionAccessStrategy cacheAccessStrategy,
      final NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy,
      final SessionFactoryImplementor factory,
      final Mapping mapping) throws HibernateException {
    super( entityBinding, cacheAccessStrategy, naturalIdRegionAccessStrategy, factory );
    // TODO: implement!!! initializing final fields to null to make compiler happy
    tableSpan = -1;
    tableNames = null;
    naturalOrderTableNames = null;
    tableKeyColumns = null;
    tableKeyColumnReaders = null;
    tableKeyColumnReaderTemplates = null;
    naturalOrderTableKeyColumns = null;
    naturalOrderTableKeyColumnReaders = null;
    naturalOrderTableKeyColumnReaderTemplates = null;
    naturalOrderCascadeDeleteEnabled = null;
    spaces = null;
    subclassClosure = null;
    subclassTableNameClosure = null;
    subclassTableKeyColumnClosure = null;
    isClassOrSuperclassTable = null;
    naturalOrderPropertyTableNumbers = null;
    propertyTableNumbers = null;
    subclassPropertyTableNumberClosure = null;
    subclassColumnTableNumberClosure = null;
    subclassFormulaTableNumberClosure = null;
    subclassTableSequentialSelect = null;
    subclassTableIsLazyClosure = null;
    discriminatorValues = null;
    notNullColumnNames = null;
    notNullColumnTableNumbers = null;
    constraintOrderedTableNames = null;
    constraintOrderedKeyColumnNames = null;
    discriminatorValue = null;
    discriminatorSQLString = null;
    discriminatorType = StandardBasicTypes.INTEGER;
    explicitDiscriminatorColumnName = null;
    discriminatorAlias = IMPLICIT_DISCRIMINATOR_ALIAS;
    coreTableSpan = -1;
    isNullableTable = null;
    subclassNamesBySubclassTable = null;
  }

  protected boolean isNullableTable(int j) {
    if ( j < coreTableSpan ) {
      return false;
    }
    return isNullableTable[j - coreTableSpan];
  }

  protected boolean isSubclassTableSequentialSelect(int j) {
    return subclassTableSequentialSelect[j] && !isClassOrSuperclassTable[j];
  }

  /*public void postInstantiate() throws MappingException {
    super.postInstantiate();
    //TODO: other lock modes?
    loader = createEntityLoader(LockMode.NONE, CollectionHelper.EMPTY_MAP);
  }*/

  public String getSubclassPropertyTableName(int i) {
    return subclassTableNameClosure[subclassPropertyTableNumberClosure[i]];
  }

  public Type getDiscriminatorType() {
    return discriminatorType;
  }

  public Object getDiscriminatorValue() {
    return discriminatorValue;
  }

  @Override
  public String getDiscriminatorSQLValue() {
    return discriminatorSQLString;
  }

  @Override
  public String getDiscriminatorColumnName() {
    return explicitDiscriminatorColumnName == null
        ? super.getDiscriminatorColumnName()
        : explicitDiscriminatorColumnName;
  }

  @Override
  public String getDiscriminatorColumnReaders() {
    return getDiscriminatorColumnName();
  }

  @Override
  public String getDiscriminatorColumnReaderTemplate() {
    return getDiscriminatorColumnName();
  }

  protected String getDiscriminatorAlias() {
    return discriminatorAlias;
  }

  public String getSubclassForDiscriminatorValue(Object value) {
    return (String) subclassesByDiscriminatorValue.get( value );
  }

  @Override
  protected void addDiscriminatorToInsert(Insert insert) {
    if ( explicitDiscriminatorColumnName != null ) {
      insert.addColumn( explicitDiscriminatorColumnName, getDiscriminatorSQLValue() );
    }
  }

  public Serializable[] getPropertySpaces() {
    return spaces; // don't need subclass tables, because they can't appear in conditions
  }


  protected String getTableName(int j) {
    return naturalOrderTableNames[j];
  }

  protected String[] getKeyColumns(int j) {
    return naturalOrderTableKeyColumns[j];
  }

  protected boolean isTableCascadeDeleteEnabled(int j) {
    return naturalOrderCascadeDeleteEnabled[j];
  }

  protected boolean isPropertyOfTable(int property, int j) {
    return naturalOrderPropertyTableNumbers[property] == j;
  }

  /**
   * Load an instance using either the <tt>forUpdateLoader</tt> or the outer joining <tt>loader</tt>,
   * depending upon the value of the <tt>lock</tt> parameter
   */
  /*public Object load(Serializable id,  Object optionalObject, LockMode lockMode, SessionImplementor session)
  throws HibernateException {

    if ( log.isTraceEnabled() ) log.trace( "Materializing entity: " + MessageHelper.infoString(this, id) );

    final UniqueEntityLoader loader = hasQueryLoader() ?
        getQueryLoader() :
        this.loader;
    try {

      final Object result = loader.load(id, optionalObject, session);

      if (result!=null) lock(id, getVersion(result), result, lockMode, session);

      return result;

    }
    catch (SQLException sqle) {
      throw new JDBCException( "could not load by id: " +  MessageHelper.infoString(this, id), sqle );
    }
  }*/
  private static final void reverse(Object[] objects, int len) {
    Object[] temp = new Object[len];
    for ( int i = 0; i < len; i++ ) {
      temp[i] = objects[len - i - 1];
    }
    for ( int i = 0; i < len; i++ ) {
      objects[i] = temp[i];
    }
  }


  /**
   * Reverse the first n elements of the incoming array
   *
   * @param objects
   * @param n
   *
   * @return New array with the first n elements in reversed order
   */
  private static String[] reverse(String[] objects, int n) {

    int size = objects.length;
    String[] temp = new String[size];

    for ( int i = 0; i < n; i++ ) {
      temp[i] = objects[n - i - 1];
    }

    for ( int i = n; i < size; i++ ) {
      temp[i] = objects[i];
    }

    return temp;
  }

  /**
   * Reverse the first n elements of the incoming array
   *
   * @param objects
   * @param n
   *
   * @return New array with the first n elements in reversed order
   */
  private static String[][] reverse(String[][] objects, int n) {
    int size = objects.length;
    String[][] temp = new String[size][];
    for ( int i = 0; i < n; i++ ) {
      temp[i] = objects[n - i - 1];
    }

    for ( int i = n; i < size; i++ ) {
      temp[i] = objects[i];
    }

    return temp;
  }


  public String fromTableFragment(String alias) {
    return getTableName() + ' ' + alias;
  }

  public String getTableName() {
    return tableNames[0];
  }

  public void addDiscriminatorToSelect(SelectFragment select, String name, String suffix) {
    if ( hasSubclasses() ) {
      if ( explicitDiscriminatorColumnName == null ) {
        select.setExtraSelectList( discriminatorFragment( name ), getDiscriminatorAlias() );
      }
      else {
        select.addColumn( name, explicitDiscriminatorColumnName, discriminatorAlias );
      }
    }
  }

  private CaseFragment discriminatorFragment(String alias) {
    CaseFragment cases = getFactory().getDialect().createCaseFragment();

    for ( int i = 0; i < discriminatorValues.length; i++ ) {
      cases.addWhenColumnNotNull(
          generateTableAlias( alias, notNullColumnTableNumbers[i] ),
          notNullColumnNames[i],
          discriminatorValues[i]
      );
    }

    return cases;
  }

  @Override
  public String filterFragment(String alias) {
    return hasWhere()
        ? " and " + getSQLWhereString( generateFilterConditionAlias( alias ) )
        : "";
  }

  @Override
  public String filterFragment(String alias, Set<String> treatAsDeclarations) {
    return filterFragment( alias );
  }

  public String generateFilterConditionAlias(String rootAlias) {
    return generateTableAlias( rootAlias, tableSpan - 1 );
  }

  public String[] getIdentifierColumnNames() {
    return tableKeyColumns[0];
  }

  public String[] getIdentifierColumnReaderTemplates() {
    return tableKeyColumnReaderTemplates[0];
  }

  public String[] getIdentifierColumnReaders() {
    return tableKeyColumnReaders[0];
  }

  public String[] toColumns(String alias, String propertyName) throws QueryException {
    if ( ENTITY_CLASS.equals( propertyName ) ) {
      // This doesn't actually seem to work but it *might*
      // work on some dbs. Also it doesn't work if there
      // are multiple columns of results because it
      // is not accounting for the suffix:
      // return new String[] { getDiscriminatorColumnName() };

      return new String[] { discriminatorFragment( alias ).toFragmentString() };
    }
    else {
      return super.toColumns( alias, propertyName );
    }
  }

  protected int[] getPropertyTableNumbersInSelect() {
    return propertyTableNumbers;
  }

  protected int getSubclassPropertyTableNumber(int i) {
    return subclassPropertyTableNumberClosure[i];
  }

  public int getTableSpan() {
    return tableSpan;
  }

  public boolean isMultiTable() {
    return true;
  }

  protected int[] getSubclassColumnTableNumberClosure() {
    return subclassColumnTableNumberClosure;
  }

  protected int[] getSubclassFormulaTableNumberClosure() {
    return subclassFormulaTableNumberClosure;
  }

  protected int[] getPropertyTableNumbers() {
    return naturalOrderPropertyTableNumbers;
  }

  protected String[] getSubclassTableKeyColumns(int j) {
    return subclassTableKeyColumnClosure[j];
  }

  public String getSubclassTableName(int j) {
    return subclassTableNameClosure[j];
  }

  public int getSubclassTableSpan() {
    return subclassTableNameClosure.length;
  }

  protected boolean isSubclassTableLazy(int j) {
    return subclassTableIsLazyClosure[j];
  }


  protected boolean isClassOrSuperclassTable(int j) {
    return isClassOrSuperclassTable[j];
  }

  @Override
  protected boolean isSubclassTableIndicatedByTreatAsDeclarations(
      int subclassTableNumber,
      Set<String> treatAsDeclarations) {
    if ( treatAsDeclarations == null || treatAsDeclarations.isEmpty() ) {
      return false;
    }

    final String[] inclusionSubclassNameClosure = getSubclassNameClosureBySubclassTable( subclassTableNumber );

    // NOTE : we assume the entire hierarchy is joined-subclass here
    for ( String subclassName : treatAsDeclarations ) {
      for ( String inclusionSubclassName : inclusionSubclassNameClosure ) {
        if ( inclusionSubclassName.equals( subclassName ) ) {
          return true;
        }
      }
    }

    return false;
  }

  private String[] getSubclassNameClosureBySubclassTable(int subclassTableNumber) {
    final int index = subclassTableNumber - getTableSpan();

    if ( index > subclassNamesBySubclassTable.length ) {
      throw new IllegalArgumentException(
          "Given subclass table number is outside expected range [" + subclassNamesBySubclassTable.length
              + "] as defined by subclassTableNameClosure/subclassClosure"
      );
    }

    return subclassNamesBySubclassTable[index];
  }

  public String getPropertyTableName(String propertyName) {
    Integer index = getEntityMetamodel().getPropertyIndexOrNull( propertyName );
    if ( index == null ) {
      return null;
    }
    return tableNames[propertyTableNumbers[index]];
  }

  public String[] getConstraintOrderedTableNameClosure() {
    return constraintOrderedTableNames;
  }

  public String[][] getContraintOrderedTableKeyColumnClosure() {
    return constraintOrderedKeyColumnNames;
  }

  public String getRootTableName() {
    return naturalOrderTableNames[0];
  }

  public String getRootTableAlias(String drivingAlias) {
    return generateTableAlias( drivingAlias, getTableId( getRootTableName(), tableNames ) );
  }

  public Declarer getSubclassPropertyDeclarer(String propertyPath) {
    if ( "class".equals( propertyPath ) ) {
      // special case where we need to force include all subclass joins
      return Declarer.SUBCLASS;
    }
    return super.getSubclassPropertyDeclarer( propertyPath );
  }

  @Override
  public int determineTableNumberForColumn(String columnName) {
    // HHH-7630: In case the naturalOrder/identifier column is explicitly given in the ordering, check here.
    for ( int i = 0, max = naturalOrderTableKeyColumns.length; i < max; i++ ) {
      final String[] keyColumns = naturalOrderTableKeyColumns[i];
      if ( ArrayHelper.contains( keyColumns, columnName ) ) {
        return naturalOrderPropertyTableNumbers[i];
      }
    }
   
    final String[] subclassColumnNameClosure = getSubclassColumnClosure();
    for ( int i = 0, max = subclassColumnNameClosure.length; i < max; i++ ) {
      final boolean quoted = subclassColumnNameClosure[i].startsWith( "\"" )
          && subclassColumnNameClosure[i].endsWith( "\"" );
      if ( quoted ) {
        if ( subclassColumnNameClosure[i].equals( columnName ) ) {
          return getSubclassColumnTableNumberClosure()[i];
        }
      }
      else {
        if ( subclassColumnNameClosure[i].equalsIgnoreCase( columnName ) ) {
          return getSubclassColumnTableNumberClosure()[i];
        }
      }
    }
    throw new HibernateException( "Could not locate table which owns column [" + columnName + "] referenced in order-by mapping" );
  }


  @Override
  public FilterAliasGenerator getFilterAliasGenerator(String rootAlias) {
    return new DynamicFilterAliasGenerator(subclassTableNameClosure, rootAlias);
  }
}
TOP

Related Classes of org.hibernate.persister.entity.JoinedSubclassEntityPersister

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.