Open Source Repository

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


org/hibernate/cfg/ResultSetMappingBinder.java
//$Id: ResultSetMappingBinder.java 10181 2006-07-28 20:17:50Z epbernard $
package org.hibernate.cfg;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;

import org.dom4j.Element;
import org.hibernate.LockMode;
import org.hibernate.MappingException;
import org.hibernate.engine.query.sql.NativeSQLQueryCollectionReturn;
import org.hibernate.engine.ResultSetMappingDefinition;
import org.hibernate.engine.query.sql.NativeSQLQueryJoinReturn;
import org.hibernate.engine.query.sql.NativeSQLQueryRootReturn;
import org.hibernate.engine.query.sql.NativeSQLQueryScalarReturn;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Value;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.ToOne;
import org.hibernate.type.Type;
import org.hibernate.type.TypeFactory;
import org.hibernate.util.ArrayHelper;
import org.hibernate.util.CollectionHelper;
import org.hibernate.util.StringHelper;

/**
 @author Emmanuel Bernard
 */
public abstract class ResultSetMappingBinder {
  /**
   * Build a ResultSetMappingDefinition given a containing element for the "return-XXX" elements
   *
   @param resultSetElem The element containing the return definitions.
   @param path No clue...
   @param mappings The current processing state.
   @return The description of the mappings...
   */
  protected static ResultSetMappingDefinition buildResultSetMappingDefinition(Element resultSetElem, String path, Mappings mappings) {
    String resultSetName = resultSetElem.attribute"name" ).getValue();
    if path != null ) {
      resultSetName = path + '.' + resultSetName;
    }
    ResultSetMappingDefinition definition = new ResultSetMappingDefinitionresultSetName );

    int cnt = 0;
    Iterator returns = resultSetElem.elementIterator();
    while returns.hasNext() ) {
      cnt++;
      Element returnElem = (Elementreturns.next();
      String name = returnElem.getName();
      if "return-scalar".equalsname ) ) {
        String column = returnElem.attributeValue"column" );
        String typeFromXML = HbmBinder.getTypeFromXMLreturnElem );
        Type type = null;
        if(typeFromXML!=null) {
          type = TypeFactory.heuristicTypetypeFromXML );
          if type == null ) {
            throw new MappingException"could not determine type " + type );
          }
        }
        definition.addQueryReturnnew NativeSQLQueryScalarReturncolumn, type ) );
      }
      else if "return".equalsname ) ) {
        definition.addQueryReturnbindReturnreturnElem, mappings, cnt ) );
      }
      else if "return-join".equalsname ) ) {
        definition.addQueryReturnbindReturnJoinreturnElem, mappings ) );
      }
      else if "load-collection".equalsname ) ) {
        definition.addQueryReturnbindLoadCollectionreturnElem, mappings ) );
      }
    }
    return definition;
  }

  private static NativeSQLQueryRootReturn bindReturn(Element returnElem, Mappings mappings, int elementCount) {
    String alias = returnElem.attributeValue"alias" );
    ifStringHelper.isEmpty(alias)) {
      alias = "alias_" + elementCount; // hack/workaround as sqlquery impl depend on having a key.
    }

    String entityName = HbmBinder.getEntityName(returnElem, mappings);
    if(entityName==null) {
      throw new MappingException"<return alias='" + alias + "'> must specify either a class or entity-name");
    }
    LockMode lockMode = getLockModereturnElem.attributeValue"lock-mode" ) );

    PersistentClass pc = mappings.getClassentityName );
    java.util.Map propertyResults = bindPropertyResults(alias, returnElem, pc, mappings );

    return new NativeSQLQueryRootReturn(
        alias,
        entityName,
        propertyResults,
        lockMode
      );
  }

  private static NativeSQLQueryJoinReturn bindReturnJoin(Element returnElem, Mappings mappings) {
    String alias = returnElem.attributeValue"alias" );
    String roleAttribute = returnElem.attributeValue"property" );
    LockMode lockMode = getLockModereturnElem.attributeValue"lock-mode" ) );
    int dot = roleAttribute.lastIndexOf'.' );
    if dot == -) {
      throw new MappingException(
          "Role attribute for sql query return [alias=" + alias +
          "] not formatted correctly {owningAlias.propertyName}"
        );
    }
    String roleOwnerAlias = roleAttribute.substring0, dot );
    String roleProperty = roleAttribute.substringdot + );

    //FIXME: get the PersistentClass
    java.util.Map propertyResults = bindPropertyResults(alias, returnElem, null, mappings );

    return new NativeSQLQueryJoinReturn(
        alias,
        roleOwnerAlias,
        roleProperty,
        propertyResults, // TODO: bindpropertyresults(alias, returnElem)
        lockMode
      );
  }

  private static NativeSQLQueryCollectionReturn bindLoadCollection(Element returnElem, Mappings mappings) {
    String alias = returnElem.attributeValue"alias" );
    String collectionAttribute = returnElem.attributeValue"role" );
    LockMode lockMode = getLockModereturnElem.attributeValue"lock-mode" ) );
    int dot = collectionAttribute.lastIndexOf'.' );
    if dot == -) {
      throw new MappingException(
          "Collection attribute for sql query return [alias=" + alias +
          "] not formatted correctly {OwnerClassName.propertyName}"
        );
    }
    String ownerClassName = HbmBinder.getClassNamecollectionAttribute.substring0, dot ), mappings );
    String ownerPropertyName = collectionAttribute.substringdot + );

    //FIXME: get the PersistentClass
    java.util.Map propertyResults = bindPropertyResults(alias, returnElem, null, mappings );

    return new NativeSQLQueryCollectionReturn(
        alias,
        ownerClassName,
        ownerPropertyName,
        propertyResults,
        lockMode
      );
  }

  private static java.util.Map bindPropertyResults(
      String alias, Element returnElement, PersistentClass pc, Mappings mappings
  ) {

    HashMap propertyresults = new HashMap()// maybe a concrete SQLpropertyresult type, but Map is exactly what is required at the moment

    Element discriminatorResult = returnElement.element("return-discriminator");
    if(discriminatorResult!=null) {
      ArrayList resultColumns = getResultColumns(discriminatorResult);
      propertyresults.put("class", ArrayHelper.toStringArray(resultColumns) );
    }
    Iterator iterator = returnElement.elementIterator("return-property");
    List properties = new ArrayList();
    List propertyNames = new ArrayList();
    while iterator.hasNext() ) {
      Element propertyresult = (Elementiterator.next();
      String name = propertyresult.attributeValue("name");
      if pc == null || name.indexOf'.'== -1) { //if dotted and not load-collection nor return-join
        //regular property
        properties.add(propertyresult);
        propertyNames.add(name);
      }
      else {
        /**
         * Reorder properties
         * 1. get the parent property
         * 2. list all the properties following the expected one in the parent property
         * 3. calculate the lowest index and insert the property
         */
        if (pc == null)
          throw new MappingException("dotted notation in <return-join> or <load_collection> not yet supported");
        int dotIndex = name.lastIndexOf'.' );
        String reducedName = name.substring0, dotIndex );
        Value value = pc.getRecursivePropertyreducedName ).getValue();
        Iterator parentPropIter;
        if value instanceof Component ) {
          Component comp = (Componentvalue;
          parentPropIter = comp.getPropertyIterator();
        }
        else if value instanceof ToOne ) {
          ToOne toOne = (ToOnevalue;
          PersistentClass referencedPc = mappings.getClasstoOne.getReferencedEntityName() );
          if toOne.getReferencedPropertyName() != null ) {
            try {
              parentPropIter = ( (ComponentreferencedPc.getRecursivePropertytoOne.getReferencedPropertyName() ).getValue() ).getPropertyIterator();
            catch (ClassCastException e) {
              throw new MappingException("dotted notation reference neither a component nor a many/one to one", e);
            }
          }
          else {
            try {
              if referencedPc.getIdentifierMapper() == null ) {
                parentPropIter = ( (ComponentreferencedPc.getIdentifierProperty().getValue() ).getPropertyIterator();
              }
              else {
                parentPropIter = referencedPc.getIdentifierMapper().getPropertyIterator();
              }
            }
            catch (ClassCastException e) {
              throw new MappingException("dotted notation reference neither a component nor a many/one to one", e);
            }
          }
        }
        else {
          throw new MappingException("dotted notation reference neither a component nor a many/one to one");
        }
        boolean hasFollowers = false;
        List followers = new ArrayList();
        while parentPropIter.hasNext() ) {
          String currentPropertyName = ( (PropertyparentPropIter.next() ).getName();
          String currentName = reducedName + '.' + currentPropertyName;
          if (hasFollowers) {
            followers.addcurrentName );
          }
          if name.equalscurrentName ) ) hasFollowers = true;
        }

        int index = propertyNames.size();
        int followersSize = followers.size();
        for (int loop = ; loop < followersSize ; loop++) {
          String follower = (Stringfollowers.get(loop);
          int currentIndex = getIndexOfFirstMatchingProperty(propertyNames, follower);
          index = currentIndex != -&& currentIndex < index ? currentIndex : index;
        }
        propertyNames.add(index, name);
        properties.add(index, propertyresult);
      }
    }

    Set uniqueReturnProperty = new HashSet();
    iterator = properties.iterator();
    while iterator.hasNext() ) {
      Element propertyresult = (Elementiterator.next();
      String name = propertyresult.attributeValue("name");
      if "class".equals(name) ) {
        throw new MappingException(
            "class is not a valid property name to use in a <return-property>, use <return-discriminator> instead"
          );
      }
      //TODO: validate existing of property with the chosen name. (secondpass )
      ArrayList allResultColumns = getResultColumns(propertyresult);

      if allResultColumns.isEmpty() ) {
        throw new MappingException(
            "return-property for alias " + alias +
            " must specify at least one column or return-column name"
          );
      }
      if uniqueReturnProperty.containsname ) ) {
        throw new MappingException(
            "duplicate return-property for property " + name +
            " on alias " + alias
          );
      }
      uniqueReturnProperty.add(name);

      // the issue here is that for <return-join/> representing an entity collection,
      // the collection element values (the property values of the associated entity)
      // are represented as 'element.{propertyname}'.  Thus the StringHelper.root()
      // here puts everything under 'element' (which additionally has significant
      // meaning).  Probably what we need to do is to something like this instead:
      //      String root = StringHelper.root( name );
      //      String key = root; // by default
      //      if ( !root.equals( name ) ) {
      //          // we had a dot
      //          if ( !root.equals( alias ) {
      //              // the root does not apply to the specific alias
      //              if ( "elements".equals( root ) {
      //                  // we specifically have a <return-join/> representing an entity collection
      //                  // and this <return-property/> is one of that entity's properties
      //                  key = name;
      //              }
      //          }
      //      }
      // but I am not clear enough on the intended purpose of this code block, especially
      // in relation to the "Reorder properties" code block above... 
//      String key = StringHelper.root( name );
      String key = name;
      ArrayList intermediateResults = (ArrayListpropertyresults.getkey );
      if (intermediateResults == null) {
        propertyresults.putkey, allResultColumns );
      }
      else {
        intermediateResults.addAllallResultColumns );
      }
    }

    Iterator entries = propertyresults.entrySet().iterator();
    while entries.hasNext() ) {
      Map.Entry entry = (Map.Entryentries.next();
      if (entry.getValue() instanceof ArrayList) {
        ArrayList list = (ArrayListentry.getValue();
        entry.setValuelist.toArraynew Stringlist.size() ] ) );
      }
    }
    return propertyresults.isEmpty() ? CollectionHelper.EMPTY_MAP : propertyresults;
  }

  private static int getIndexOfFirstMatchingProperty(List propertyNames, String follower) {
    int propertySize = propertyNames.size();
    for (int propIndex = ; propIndex < propertySize ; propIndex++) {
      if ( ( (StringpropertyNames.get(propIndex) ).startsWithfollower ) ) {
        return propIndex;
      }
    }
    return -1;
  }

  private static ArrayList getResultColumns(Element propertyresult) {
    String column = unquote(propertyresult.attributeValue("column"));
    ArrayList allResultColumns = new ArrayList();
    if(column!=nullallResultColumns.add(column);
    Iterator resultColumns = propertyresult.elementIterator("return-column");
    while resultColumns.hasNext() ) {
      Element element = (ElementresultColumns.next();
      allResultColumns.addunquote(element.attributeValue("name")) );
    }
    return allResultColumns;
  }

  private static String unquote(String name) {
    if (name!=null && name.charAt(0)=='`') {
      name=name.substring1, name.length()-);
    }
    return name;
  }

  private static LockMode getLockMode(String lockMode) {
    if lockMode == null || "read".equalslockMode ) ) {
      return LockMode.READ;
    }
    else if "none".equalslockMode ) ) {
      return LockMode.NONE;
    }
    else if "upgrade".equalslockMode ) ) {
      return LockMode.UPGRADE;
    }
    else if "upgrade-nowait".equalslockMode ) ) {
      return LockMode.UPGRADE_NOWAIT;
    }
    else if "write".equalslockMode ) ) {
      return LockMode.WRITE;
    }
    else if "force".equalslockMode ) ) {
      return LockMode.FORCE;
    }
    else {
      throw new MappingException"unknown lockmode" );
    }
  }
}