Open Source Repository

Home /hibernate/hibernate-3.2.6.ga | Repository Home


org/hibernate/loader/custom/CustomLoader.java
//$Id: CustomLoader.java 10087 2006-07-06 12:21:30Z [email protected] $
package org.hibernate.loader.custom;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.HashSet;

import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.QueryException;
import org.hibernate.ScrollableResults;
import org.hibernate.engine.QueryParameters;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.hql.HolderInstantiator;
import org.hibernate.loader.CollectionAliases;
import org.hibernate.loader.EntityAliases;
import org.hibernate.loader.Loader;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.Loadable;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.transform.ResultTransformer;
import org.hibernate.type.Type;
import org.hibernate.type.TypeFactory;
import org.hibernate.type.EntityType;
import org.hibernate.type.CollectionType;
import org.hibernate.util.ArrayHelper;

/**
 * Extension point for loaders which use a SQL result set with "unexpected" column aliases.
 
 @author Gavin King
 @author Steve Ebersole
 */
public class CustomLoader extends Loader {
  
  // Currently *not* cachable if autodiscover types is in effect (e.g. "select * ...")

  private final String sql;
  private final Set querySpaces = new HashSet();
  private final Map namedParameterBindPoints;

  private final Queryable[] entityPersisters;
  private final int[] entiytOwners;
  private final EntityAliases[] entityAliases;

  private final QueryableCollection[] collectionPersisters;
  private final int[] collectionOwners;
  private final CollectionAliases[] collectionAliases;

  private final LockMode[] lockModes;
//  private final String[] sqlAliases;
//  private final String[] sqlAliasSuffixes;
  private final ResultRowProcessor rowProcessor;

  // this is only needed (afaict) for processing results from the query cache;
  // however, this cannot possibly work in the case of discovered types...
  private Type[] resultTypes;

  // this is only needed (afaict) for ResultTransformer processing...
  private String[] transformerAliases;


  public CustomLoader(CustomQuery customQuery, SessionFactoryImplementor factory) {
    superfactory );

    this.sql = customQuery.getSQL();
    this.querySpaces.addAllcustomQuery.getQuerySpaces() );
    this.namedParameterBindPoints = customQuery.getNamedParameterBindPoints();

    List entityPersisters = new ArrayList();
    List entityOwners = new ArrayList();
    List entityAliases = new ArrayList();

    List collectionPersisters = new ArrayList();
    List collectionOwners = new ArrayList();
    List collectionAliases = new ArrayList();

    List lockModes = new ArrayList();
    List resultColumnProcessors = new ArrayList();
    List nonScalarReturnList = new ArrayList();
    List resultTypes = new ArrayList();
    List specifiedAliases = new ArrayList();
    int returnableCounter = 0;
    boolean hasScalars = false;

    Iterator itr = customQuery.getCustomQueryReturns().iterator();
    while itr.hasNext() ) {
      final Return rtn = Return itr.next();
      if rtn instanceof ScalarReturn ) {
        ScalarReturn scalarRtn = ScalarReturn rtn;
        resultTypes.addscalarRtn.getType() );
        specifiedAliases.addscalarRtn.getColumnAlias() );
        resultColumnProcessors.add(
            new ScalarResultColumnProcessor(
                scalarRtn.getColumnAlias(),
                scalarRtn.getType()
            )
        );
        hasScalars = true;
      }
      else if rtn instanceof RootReturn ) {
        RootReturn rootRtn = RootReturn rtn;
        Queryable persister = Queryable factory.getEntityPersisterrootRtn.getEntityName() );
        entityPersisters.addpersister );
        lockModes.addrootRtn.getLockMode() );
        resultColumnProcessors.addnew NonScalarResultColumnProcessorreturnableCounter++ ) );
        nonScalarReturnList.addrtn );
        entityOwners.addnew Integer-) );
        resultTypes.addpersister.getType() );
        specifiedAliases.addrootRtn.getAlias() );
        entityAliases.addrootRtn.getEntityAliases() );
        ArrayHelper.addAllquerySpaces, persister.getQuerySpaces() );
      }
      else if rtn instanceof CollectionReturn ) {
        CollectionReturn collRtn = CollectionReturn rtn;
        String role = collRtn.getOwnerEntityName() "." + collRtn.getOwnerProperty();
        QueryableCollection persister = QueryableCollection factory.getCollectionPersisterrole );
        collectionPersisters.addpersister );
        lockModes.addcollRtn.getLockMode() );
        resultColumnProcessors.addnew NonScalarResultColumnProcessorreturnableCounter++ ) );
        nonScalarReturnList.addrtn );
        collectionOwners.addnew Integer-) );
        resultTypes.addpersister.getType() );
        specifiedAliases.addcollRtn.getAlias() );
        collectionAliases.addcollRtn.getCollectionAliases() );
        // determine if the collection elements are entities...
        Type elementType = persister.getElementType();
        if elementType.isEntityType() ) {
          Queryable elementPersister = Queryable ) ( ( EntityType elementType ).getAssociatedJoinablefactory );
          entityPersisters.addelementPersister );
          entityOwners.addnew Integer-) );
          entityAliases.addcollRtn.getElementEntityAliases() );
          ArrayHelper.addAllquerySpaces, elementPersister.getQuerySpaces() );
        }
      }
      else if rtn instanceof EntityFetchReturn ) {
        EntityFetchReturn fetchRtn = EntityFetchReturn rtn;
        NonScalarReturn ownerDescriptor = fetchRtn.getOwner();
        int ownerIndex = nonScalarReturnList.indexOfownerDescriptor );
        entityOwners.addnew IntegerownerIndex ) );
        lockModes.addfetchRtn.getLockMode() );
        Queryable ownerPersister = determineAppropriateOwnerPersisterownerDescriptor );
        EntityType fetchedType = EntityType ownerPersister.getPropertyTypefetchRtn.getOwnerProperty() );
        String entityName = fetchedType.getAssociatedEntityNamegetFactory() );
        Queryable persister = Queryable factory.getEntityPersisterentityName );
        entityPersisters.addpersister );
        nonScalarReturnList.addrtn );
        specifiedAliases.addfetchRtn.getAlias() );
        entityAliases.addfetchRtn.getEntityAliases() );
        ArrayHelper.addAllquerySpaces, persister.getQuerySpaces() );
      }
      else if rtn instanceof CollectionFetchReturn ) {
        CollectionFetchReturn fetchRtn = CollectionFetchReturn rtn;
        NonScalarReturn ownerDescriptor = fetchRtn.getOwner();
        int ownerIndex = nonScalarReturnList.indexOfownerDescriptor );
        collectionOwners.addnew IntegerownerIndex ) );
        lockModes.addfetchRtn.getLockMode() );
        Queryable ownerPersister = determineAppropriateOwnerPersisterownerDescriptor );
        String role = ownerPersister.getEntityName() '.' + fetchRtn.getOwnerProperty();
        QueryableCollection persister = QueryableCollection factory.getCollectionPersisterrole );
        collectionPersisters.addpersister );
        nonScalarReturnList.addrtn );
        specifiedAliases.addfetchRtn.getAlias() );
        collectionAliases.addfetchRtn.getCollectionAliases() );
        // determine if the collection elements are entities...
        Type elementType = persister.getElementType();
        if elementType.isEntityType() ) {
          Queryable elementPersister = Queryable ) ( ( EntityType elementType ).getAssociatedJoinablefactory );
          entityPersisters.addelementPersister );
          entityOwners.addnew IntegerownerIndex ) );
          entityAliases.addfetchRtn.getElementEntityAliases() );
          ArrayHelper.addAllquerySpaces, elementPersister.getQuerySpaces() );
        }
      }
      else {
        throw new HibernateException"unexpected custom query return type : " + rtn.getClass().getName() );
      }
    }

    this.entityPersisters = new QueryableentityPersisters.size() ];
    for int i = 0; i < entityPersisters.size(); i++ ) {
      this.entityPersisters[iQueryable entityPersisters.get);
    }
    this.entiytOwners = ArrayHelper.toIntArrayentityOwners );
    this.entityAliases = new EntityAliasesentityAliases.size() ];
    for int i = 0; i < entityAliases.size(); i++ ) {
      this.entityAliases[iEntityAliases entityAliases.get);
    }

    this.collectionPersisters = new QueryableCollectioncollectionPersisters.size() ];
    for int i = 0; i < collectionPersisters.size(); i++ ) {
      this.collectionPersisters[iQueryableCollection collectionPersisters.get);
    }
    this.collectionOwners = ArrayHelper.toIntArraycollectionOwners );
    this.collectionAliases = new CollectionAliasescollectionAliases.size() ];
    for int i = 0; i < collectionAliases.size(); i++ ) {
      this.collectionAliases[iCollectionAliases collectionAliases.get);
    }

    this.lockModes = new LockModelockModes.size() ];
    for int i = 0; i < lockModes.size(); i++ ) {
      this.lockModes[iLockMode lockModes.get);
    }

    this.resultTypes = ArrayHelper.toTypeArrayresultTypes );
    this.transformerAliases = ArrayHelper.toStringArrayspecifiedAliases );

    this.rowProcessor = new ResultRowProcessor(
        hasScalars,
            ResultColumnProcessor[] ) resultColumnProcessors.toArraynew ResultColumnProcessorresultColumnProcessors.size() ] )
    );
  }

  private Queryable determineAppropriateOwnerPersister(NonScalarReturn ownerDescriptor) {
    String entityName = null;
    if ownerDescriptor instanceof RootReturn ) {
      entityName = ( ( RootReturn ownerDescriptor ).getEntityName();
    }
    else if ownerDescriptor instanceof CollectionReturn ) {
      CollectionReturn collRtn = CollectionReturn ownerDescriptor;
      String role = collRtn.getOwnerEntityName() "." + collRtn.getOwnerProperty();
      CollectionPersister persister = getFactory().getCollectionPersisterrole );
      EntityType ownerType = EntityType persister.getElementType();
      entityName = ownerType.getAssociatedEntityNamegetFactory() );
    }
    else if ownerDescriptor instanceof FetchReturn ) {
      FetchReturn fetchRtn = FetchReturn ownerDescriptor;
      Queryable persister = determineAppropriateOwnerPersisterfetchRtn.getOwner() );
      Type ownerType = persister.getPropertyTypefetchRtn.getOwnerProperty() );
      if ownerType.isEntityType() ) {
        entityName = ( ( EntityType ownerType ).getAssociatedEntityNamegetFactory() );
      }
      else if ownerType.isCollectionType() ) {
        Type ownerCollectionElementType = ( ( CollectionType ownerType ).getElementTypegetFactory() );
        if ownerCollectionElementType.isEntityType() ) {
          entityName = ( ( EntityType ownerCollectionElementType ).getAssociatedEntityNamegetFactory() );
        }
      }
    }

    if entityName == null ) {
      throw new HibernateException"Could not determine fetch owner : " + ownerDescriptor );
    }

    return Queryable getFactory().getEntityPersisterentityName );
  }

  protected String getQueryIdentifier() {
    return sql;
  }

  protected String getSQLString() {
    return sql;
  }

  public Set getQuerySpaces() {
    return querySpaces;
  }

  protected LockMode[] getLockModes(Map lockModesMap) {
    return lockModes;
  }

  protected Loadable[] getEntityPersisters() {
    return entityPersisters;
  }

  protected CollectionPersister[] getCollectionPersisters() {
    return collectionPersisters;
  }

  protected int[] getCollectionOwners() {
    return collectionOwners;
  }
  
  protected int[] getOwners() {
    return entiytOwners;
  }

  public List list(SessionImplementor session, QueryParameters queryParametersthrows HibernateException {
    return listsession, queryParameters, querySpaces, resultTypes );
  }

  public ScrollableResults scroll(
      final QueryParameters queryParameters,
      final SessionImplementor sessionthrows HibernateException {    
    return scroll(
        queryParameters,
        resultTypes,
        getHolderInstantiatorqueryParameters.getResultTransformer(), getReturnAliasesForTransformer() ),
        session
    );
  }
  
  static private HolderInstantiator getHolderInstantiator(ResultTransformer resultTransformer, String[] queryReturnAliases) {
    if resultTransformer != null ) {
      return HolderInstantiator.NOOP_INSTANTIATOR;
    }
    else {
      return new HolderInstantiator(resultTransformer, queryReturnAliases);
    }
  }
  
  protected Object getResultColumnOrRow(
      Object[] row,
          ResultTransformer transformer,
          ResultSet rs,
          SessionImplementor sessionthrows SQLException, HibernateException {
    return rowProcessor.buildResultRowrow, rs, transformer != null, session );
  }

  protected List getResultList(List results, ResultTransformer resultTransformerthrows QueryException {
    // meant to handle dynamic instantiation queries...(Copy from QueryLoader)
    HolderInstantiator holderInstantiator = HolderInstantiator.getHolderInstantiator(
        null,
        resultTransformer,
        getReturnAliasesForTransformer()
    );
    if holderInstantiator.isRequired() ) {
      for int i = 0; i < results.size(); i++ ) {
        Object[] row = Object[] ) results.get);
        Object result = holderInstantiator.instantiate(row);
        results.seti, result );
      }
      
      return resultTransformer.transformList(results);
    }
    else {
      return results;
    }
  }

  private String[] getReturnAliasesForTransformer() {
    return transformerAliases;
  }
  
  protected EntityAliases[] getEntityAliases() {
    return entityAliases;
  }

  protected CollectionAliases[] getCollectionAliases() {
    return collectionAliases;
  }
  
  public int[] getNamedParameterLocs(String namethrows QueryException {
    Object loc = namedParameterBindPoints.getname );
    if loc == null ) {
      throw new QueryException(
          "Named parameter does not appear in Query: " + name,
          sql
      );
    }
    if loc instanceof Integer ) {
      return new int[] { ( ( Integer loc ).intValue() };
    }
    else {
      return ArrayHelper.toIntArray( ( List loc );
    }
  }


  public class ResultRowProcessor {
    private final boolean hasScalars;
    private ResultColumnProcessor[] columnProcessors;

    public ResultRowProcessor(boolean hasScalars, ResultColumnProcessor[] columnProcessors) {
      this.hasScalars = hasScalars || columnProcessors == null || columnProcessors.length == );
      this.columnProcessors = columnProcessors;
    }

    public void prepareForAutoDiscovery(Metadata metadatathrows SQLException {
      if columnProcessors == null || columnProcessors.length == ) {
        int columns = metadata.getColumnCount();
        columnProcessors = new ResultColumnProcessorcolumns ];
        for int i = 1; i <= columns; i++ ) {
          columnProcessorsi - new ScalarResultColumnProcessor);
        }

      }
    }

    /**
     * Build a logical result row.
     <p/>
     * At this point, Loader has already processed all non-scalar result data.  We
     * just need to account for scalar result data here...
     *
     @param data Entity data defined as "root returns" and already handled by the
     * normal Loader mechanism.
     @param resultSet The JDBC result set (positioned at the row currently being processed).
     @param hasTransformer Does this query have an associated {@link ResultTransformer}
     @param session The session from which the query request originated.
     @return The logical result row
     @throws SQLException
     @throws HibernateException
     */
    public Object buildResultRow(
        Object[] data,
        ResultSet resultSet,
        boolean hasTransformer,
        SessionImplementor sessionthrows SQLException, HibernateException {
      Object[] resultRow;
      if !hasScalars ) {
        resultRow = data;
      }
      else {
        // build an array with indices equal to the total number
        // of actual returns in the result Hibernate will return
        // for this query (scalars + non-scalars)
        resultRow = new ObjectcolumnProcessors.length ];
        for int i = 0; i < columnProcessors.length; i++ ) {
          resultRow[i= columnProcessors[i].extractdata, resultSet, session );
        }
      }

      return hasTransformer )
             ? resultRow
             resultRow.length == )
               ? resultRow[0]
               : resultRow;
    }
  }

  private static interface ResultColumnProcessor {
    public Object extract(Object[] data, ResultSet resultSet, SessionImplementor sessionthrows SQLException, HibernateException;
    public void performDiscovery(Metadata metadata, List types, List aliasesthrows SQLException, HibernateException;
  }

  public class NonScalarResultColumnProcessor implements ResultColumnProcessor {
    private final int position;

    public NonScalarResultColumnProcessor(int position) {
      this.position = position;
    }

    public Object extract(
        Object[] data,
        ResultSet resultSet,
        SessionImplementor sessionthrows SQLException, HibernateException {
      return dataposition ];
    }

    public void performDiscovery(Metadata metadata, List types, List aliases) {
    }

  }

  public class ScalarResultColumnProcessor implements ResultColumnProcessor {
    private int position = -1;
    private String alias;
    private Type type;

    public ScalarResultColumnProcessor(int position) {
      this.position = position;
    }

    public ScalarResultColumnProcessor(String alias, Type type) {
      this.alias = alias;
      this.type = type;
    }

    public Object extract(
        Object[] data,
        ResultSet resultSet,
        SessionImplementor sessionthrows SQLException, HibernateException {
      return type.nullSafeGetresultSet, alias, session, null );
    }

    public void performDiscovery(Metadata metadata, List types, List aliasesthrows SQLException {
      if alias == null ) {
        alias = metadata.getColumnNameposition );
      }
      else if position < ) {
        position = metadata.resolveColumnPositionalias );
      }
      if type == null ) {
        type = metadata.getHibernateTypeposition );
      }
      types.addtype );
      aliases.addalias );
    }
  }

  protected void autoDiscoverTypes(ResultSet rs) {
    try {
      Metadata metadata = new MetadatagetFactory(), rs );
      List aliases = new ArrayList();
      List types = new ArrayList();

      rowProcessor.prepareForAutoDiscoverymetadata );

      for int i = 0; i < rowProcessor.columnProcessors.length; i++ ) {
        rowProcessor.columnProcessors[i].performDiscoverymetadata, types, aliases );
      }

      resultTypes = ArrayHelper.toTypeArraytypes );
      transformerAliases = ArrayHelper.toStringArrayaliases );
    }
    catch SQLException e ) {
      throw new HibernateException"Exception while trying to autodiscover types.", e );
    }
  }

  private static class Metadata {
    private final SessionFactoryImplementor factory;
    private final ResultSet resultSet;
    private final ResultSetMetaData resultSetMetaData;

    public Metadata(SessionFactoryImplementor factory, ResultSet resultSetthrows HibernateException {
      try {
        this.factory = factory;
        this.resultSet = resultSet;
        this.resultSetMetaData = resultSet.getMetaData();
      }
      catchSQLException e ) {
        throw new HibernateException"Could not extract result set metadata", e );
      }
    }

    public int getColumnCount() throws HibernateException {
      try {
        return resultSetMetaData.getColumnCount();
      }
      catchSQLException e ) {
        throw new HibernateException"Could not determine result set column count", e );
      }
    }

    public int resolveColumnPosition(String columnNamethrows HibernateException {
      try {
        return resultSet.findColumncolumnName );
      }
      catchSQLException e ) {
        throw new HibernateException"Could not resolve column name in result set [" + columnName + "]", e );
      }
    }

    public String getColumnName(int positionthrows HibernateException {
      try {
        return resultSetMetaData.getColumnNameposition );
      }
      catchSQLException e ) {
        throw new HibernateException"Could not resolve column name [" + position + "]", e );
      }
    }

    public Type getHibernateType(int columnPosthrows SQLException {
      int columnType = resultSetMetaData.getColumnTypecolumnPos );
      int scale = resultSetMetaData.getScalecolumnPos );
      int precision = resultSetMetaData.getPrecisioncolumnPos );
      return TypeFactory.heuristicType(
          factory.getDialect().getHibernateTypeName(
              columnType,
              precision,
              precision,
              scale
          )
      );
    }
  }
}