Package org.hibernate.hql.internal.classic

Source Code of org.hibernate.hql.internal.classic.PathExpressionParser

/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
*
* 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.hql.internal.classic;
import java.util.LinkedList;
import java.util.Map;

import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.engine.internal.JoinSequence;
import org.hibernate.hql.internal.CollectionSubqueryFactory;
import org.hibernate.persister.collection.CollectionPropertyMapping;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.PropertyMapping;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.sql.JoinType;
import org.hibernate.type.AssociationType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;

/**
* Parses an expression of the form foo.bar.baz and builds up an expression
* involving two less table joins than there are path components.
*/
public class PathExpressionParser implements Parser {

  //TODO: this class does too many things! we need a different
  //kind of path expression parser for each of the diffferent
  //ways in which path expressions can occur

  //We should actually rework this class to not implement Parser
  //and just process path expressions in the most convenient way.

  //The class is now way to complex!

  private int dotcount;
  private String currentName;
  private String currentProperty;
  private String oneToOneOwnerName;
  private AssociationType ownerAssociationType;
  private String[] columns;
  private String collectionName;
  private String collectionOwnerName;
  private String collectionRole;
  private final StringBuilder componentPath = new StringBuilder();
  private Type type;
  private final StringBuilder path = new StringBuilder();
  private boolean ignoreInitialJoin;
  private boolean continuation;
  private JoinType joinType = JoinType.INNER_JOIN; //default mode
  private boolean useThetaStyleJoin = true;
  private PropertyMapping currentPropertyMapping;
  private JoinSequence joinSequence;

  private boolean expectingCollectionIndex;
  private LinkedList collectionElements = new LinkedList();

  void setJoinType(JoinType joinType) {
    this.joinType = joinType;
  }

  void setUseThetaStyleJoin(boolean useThetaStyleJoin) {
    this.useThetaStyleJoin = useThetaStyleJoin;
  }

  private void addJoin(String name, AssociationType joinableType) throws QueryException {
    try {
      joinSequence.addJoin( joinableType, name, joinType, currentColumns() );
    }
    catch ( MappingException me ) {
      throw new QueryException( me );
    }
  }

  private void addJoin(String name, AssociationType joinableType, String[] foreignKeyColumns) throws QueryException {
    try {
      joinSequence.addJoin( joinableType, name, joinType, foreignKeyColumns );
    }
    catch ( MappingException me ) {
      throw new QueryException( me );
    }
  }

  String continueFromManyToMany(String entityName, String[] joinColumns, QueryTranslatorImpl q) throws QueryException {
    start( q );
    continuation = true;
    currentName = q.createNameFor( entityName );
    q.addType( currentName, entityName );
    Queryable classPersister = q.getEntityPersister( entityName );
    //QueryJoinFragment join = q.createJoinFragment(useThetaStyleJoin);
    addJoin( currentName, q.getFactory().getTypeResolver().getTypeFactory().manyToOne( entityName ), joinColumns );
    currentPropertyMapping = classPersister;
    return currentName;
  }

  public void ignoreInitialJoin() {
    ignoreInitialJoin = true;
  }

  public void token(String token, QueryTranslatorImpl q) throws QueryException {

    if ( token != null ) path.append( token );

    String alias = q.getPathAlias( path.toString() );
    if ( alias != null ) {
      reset( q ); //reset the dotcount (but not the path)
      currentName = alias; //after reset!
      currentPropertyMapping = q.getPropertyMapping( currentName );
      if ( !ignoreInitialJoin ) {
        JoinSequence ojf = q.getPathJoin( path.toString() );
        try {
          joinSequence.addCondition( ojf.toJoinFragment( q.getEnabledFilters(), true ).toWhereFragmentString() ); //after reset!
        }
        catch ( MappingException me ) {
          throw new QueryException( me );
        }
        // we don't need to worry about any condition in the ON clause
        // here (toFromFragmentString), since anything in the ON condition
        // is already applied to the whole query
      }
    }
    else if ( ".".equals( token ) ) {
      dotcount++;
    }
    else {
      if ( dotcount == 0 ) {
        if ( !continuation ) {
          if ( !q.isName( token ) ) throw new QueryException( "undefined alias: " + token );
          currentName = token;
          currentPropertyMapping = q.getPropertyMapping( currentName );
        }
      }
      else if ( dotcount == 1 ) {
        if ( currentName != null ) {
          currentProperty = token;
        }
        else if ( collectionName != null ) {
          //processCollectionProperty(token, q.getCollectionPersister(collectionRole), collectionName);
          continuation = false;
        }
        else {
          throw new QueryException( "unexpected" );
        }
      }
      else { // dotcount>=2

        // Do the corresponding RHS
        Type propertyType = getPropertyType();

        if ( propertyType == null ) {
          throw new QueryException( "unresolved property: " + path );
        }

        if ( propertyType.isComponentType() ) {
          dereferenceComponent( token );
        }
        else if ( propertyType.isEntityType() ) {
          if ( !isCollectionValued() ) dereferenceEntity( token, ( EntityType ) propertyType, q );
        }
        else if ( propertyType.isCollectionType() ) {
          dereferenceCollection( token, ( ( CollectionType ) propertyType ).getRole(), q );

        }
        else {
          if ( token != null ) throw new QueryException( "dereferenced: " + path );
        }

      }
    }
  }

  private void dereferenceEntity(String propertyName, EntityType propertyType, QueryTranslatorImpl q)
      throws QueryException {
    //NOTE: we avoid joining to the next table if the named property is just the foreign key value

    //if its "id"
    boolean isIdShortcut = EntityPersister.ENTITY_ID.equals( propertyName ) &&
        propertyType.isReferenceToPrimaryKey();

    //or its the id property name
    final String idPropertyName;
    try {
      idPropertyName = propertyType.getIdentifierOrUniqueKeyPropertyName( q.getFactory() );
    }
    catch ( MappingException me ) {
      throw new QueryException( me );
    }
    boolean isNamedIdPropertyShortcut = idPropertyName != null
        && idPropertyName.equals( propertyName )
        && propertyType.isReferenceToPrimaryKey();


    if ( isIdShortcut || isNamedIdPropertyShortcut ) {
      // special shortcut for id properties, skip the join!
      // this must only occur at the _end_ of a path expression
      if ( componentPath.length() > 0 ) componentPath.append( '.' );
      componentPath.append( propertyName );
    }
    else {
      String entityClass = propertyType.getAssociatedEntityName();
      String name = q.createNameFor( entityClass );
      q.addType( name, entityClass );
      addJoin( name, propertyType );
      if ( propertyType.isOneToOne() ) oneToOneOwnerName = currentName;
      ownerAssociationType = propertyType;
      currentName = name;
      currentProperty = propertyName;
      q.addPathAliasAndJoin( path.substring( 0, path.toString().lastIndexOf( '.' ) ), name, joinSequence.copy() );
      componentPath.setLength( 0 );
      currentPropertyMapping = q.getEntityPersister( entityClass );
    }
  }

  private void dereferenceComponent(String propertyName) {
    if ( propertyName != null ) {
      if ( componentPath.length() > 0 ) componentPath.append( '.' );
      componentPath.append( propertyName );
    }
  }

  private void dereferenceCollection(String propertyName, String role, QueryTranslatorImpl q) throws QueryException {
    collectionRole = role;
    QueryableCollection collPersister = q.getCollectionPersister( role );
    String name = q.createNameForCollection( role );
    addJoin( name, collPersister.getCollectionType() );
    //if ( collPersister.hasWhere() ) join.addCondition( collPersister.getSQLWhereString(name) );
    collectionName = name;
    collectionOwnerName = currentName;
    currentName = name;
    currentProperty = propertyName;
    componentPath.setLength( 0 );
    currentPropertyMapping = new CollectionPropertyMapping( collPersister );
  }

  private String getPropertyPath() {
    if ( currentProperty == null ) {
      return EntityPersister.ENTITY_ID;
    }
    else {
      if ( componentPath.length() > 0 ) {
        return new StringBuilder()
            .append( currentProperty )
            .append( '.' )
            .append( componentPath.toString() )
            .toString();
      }
      else {
        return currentProperty;
      }
    }
  }

  private PropertyMapping getPropertyMapping() {
    return currentPropertyMapping;
  }

  private void setType() throws QueryException {
    if ( currentProperty == null ) {
      type = getPropertyMapping().getType();
    }
    else {
      type = getPropertyType();
    }
  }

  protected Type getPropertyType() throws QueryException {
    String propertyPath = getPropertyPath();
    Type propertyType = getPropertyMapping().toType( propertyPath );
    if ( propertyType == null ) {
      throw new QueryException( "could not resolve property type: " + propertyPath );
    }
    return propertyType;
  }

  protected String[] currentColumns() throws QueryException {
    String propertyPath = getPropertyPath();
    String[] propertyColumns = getPropertyMapping().toColumns( currentName, propertyPath );
    if ( propertyColumns == null ) {
      throw new QueryException( "could not resolve property columns: " + propertyPath );
    }
    return propertyColumns;
  }

  private void reset(QueryTranslatorImpl q) {
    //join = q.createJoinFragment(useThetaStyleJoin);
    dotcount = 0;
    currentName = null;
    currentProperty = null;
    collectionName = null;
    collectionRole = null;
    componentPath.setLength( 0 );
    type = null;
    collectionName = null;
    columns = null;
    expectingCollectionIndex = false;
    continuation = false;
    currentPropertyMapping = null;
  }

  public void start(QueryTranslatorImpl q) {
    if ( !continuation ) {
      reset( q );
      path.setLength( 0 );
      joinSequence = new JoinSequence( q.getFactory() ).setUseThetaStyle( useThetaStyleJoin );
    }
  }

  public void end(QueryTranslatorImpl q) throws QueryException {
    ignoreInitialJoin = false;

    Type propertyType = getPropertyType();
    if ( propertyType != null && propertyType.isCollectionType() ) {
      collectionRole = ( ( CollectionType ) propertyType ).getRole();
      collectionName = q.createNameForCollection( collectionRole );
      prepareForIndex( q );
    }
    else {
      columns = currentColumns();
      setType();
    }

    //important!!
    continuation = false;

  }

  private void prepareForIndex(QueryTranslatorImpl q) throws QueryException {

    QueryableCollection collPersister = q.getCollectionPersister( collectionRole );

    if ( !collPersister.hasIndex() ) throw new QueryException( "unindexed collection before []: " + path );
    String[] indexCols = collPersister.getIndexColumnNames();
    if ( indexCols.length != 1 ) throw new QueryException( "composite-index appears in []: " + path );
    //String[] keyCols = collPersister.getKeyColumnNames();

    JoinSequence fromJoins = new JoinSequence( q.getFactory() )
        .setUseThetaStyle( useThetaStyleJoin )
        .setRoot( collPersister, collectionName )
        .setNext( joinSequence.copy() );

    if ( !continuation ) addJoin( collectionName, collPersister.getCollectionType() );

    joinSequence.addCondition( collectionName + '.' + indexCols[0] + " = " ); //TODO: get SQL rendering out of here

    CollectionElement elem = new CollectionElement();
    elem.elementColumns = collPersister.getElementColumnNames(collectionName);
    elem.elementType = collPersister.getElementType();
    elem.isOneToMany = collPersister.isOneToMany();
    elem.alias = collectionName;
    elem.joinSequence = joinSequence;
    collectionElements.addLast( elem );
    setExpectingCollectionIndex();

    q.addCollection( collectionName, collectionRole );
    q.addFromJoinOnly( collectionName, fromJoins );
  }

  static final class CollectionElement {
    Type elementType;
    boolean isOneToMany;
    String alias;
    String[] elementColumns;
    JoinSequence joinSequence;
    StringBuilder indexValue = new StringBuilder();
  }

  public CollectionElement lastCollectionElement() {
    return ( CollectionElement ) collectionElements.removeLast();
  }

  public void setLastCollectionElementIndexValue(String value) {
    ( ( CollectionElement ) collectionElements.getLast() ).indexValue.append( value );
  }

  public boolean isExpectingCollectionIndex() {
    return expectingCollectionIndex;
  }

  protected void setExpectingCollectionIndex() throws QueryException {
    expectingCollectionIndex = true;
  }

  public JoinSequence getWhereJoin() {
    return joinSequence;
  }

  public String getWhereColumn() throws QueryException {
    if ( columns.length != 1 ) {
      throw new QueryException( "path expression ends in a composite value: " + path );
    }
    return columns[0];
  }

  public String[] getWhereColumns() {
    return columns;
  }

  public Type getWhereColumnType() {
    return type;
  }

  public String getName() {
    return currentName == null ? collectionName : currentName;
  }


  public String getCollectionSubquery(Map enabledFilters) throws QueryException {
    return CollectionSubqueryFactory.createCollectionSubquery( joinSequence, enabledFilters, currentColumns() );
  }

  public boolean isCollectionValued() throws QueryException {
    //TODO: is there a better way?
    return collectionName != null && !getPropertyType().isCollectionType();
  }

  public void addAssociation(QueryTranslatorImpl q) throws QueryException {
    q.addJoin( getName(), joinSequence );
  }

  public String addFromAssociation(QueryTranslatorImpl q) throws QueryException {
    if ( isCollectionValued() ) {
      return addFromCollection( q );
    }
    else {
      q.addFrom( currentName, joinSequence );
      return currentName;
    }
  }

  public String addFromCollection(QueryTranslatorImpl q) throws QueryException {
    Type collectionElementType = getPropertyType();

    if ( collectionElementType == null ) {
      throw new QueryException( "must specify 'elements' for collection valued property in from clause: " + path );
    }

    if ( collectionElementType.isEntityType() ) {
      // an association
      QueryableCollection collectionPersister = q.getCollectionPersister( collectionRole );
      Queryable entityPersister = ( Queryable ) collectionPersister.getElementPersister();
      String clazz = entityPersister.getEntityName();

      final String elementName;
      if ( collectionPersister.isOneToMany() ) {
        elementName = collectionName;
        //allow index() function:
        q.decoratePropertyMapping( elementName, collectionPersister );
      }
      else { //many-to-many
        q.addCollection( collectionName, collectionRole );
        elementName = q.createNameFor( clazz );
        addJoin( elementName, ( AssociationType ) collectionElementType );
      }
      q.addFrom( elementName, clazz, joinSequence );
      currentPropertyMapping = new CollectionPropertyMapping( collectionPersister );
      return elementName;
    }
    else {
      // collections of values
      q.addFromCollection( collectionName, collectionRole, joinSequence );
      return collectionName;
    }

  }

  String getCollectionName() {
    return collectionName;
  }

  String getCollectionRole() {
    return collectionRole;
  }

  String getCollectionOwnerName() {
    return collectionOwnerName;
  }

  String getOneToOneOwnerName() {
    return oneToOneOwnerName;
  }

  AssociationType getOwnerAssociationType() {
    return ownerAssociationType;
  }

  String getCurrentProperty() {
    return currentProperty;
  }

  String getCurrentName() {
    return currentName;
  }

  public void fetch(QueryTranslatorImpl q, String entityName) throws QueryException {
    if ( isCollectionValued() ) {
      q.setCollectionToFetch( getCollectionRole(), getCollectionName(), getCollectionOwnerName(), entityName );
    }
    else {
      q.addEntityToFetch( entityName, getOneToOneOwnerName(), getOwnerAssociationType() );
    }
  }
}
TOP

Related Classes of org.hibernate.hql.internal.classic.PathExpressionParser

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.