Package org.hibernate.engine.internal

Source Code of org.hibernate.engine.internal.JoinSequence

/*
* 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.engine.internal;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hibernate.MappingException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.sql.JoinFragment;
import org.hibernate.sql.JoinType;
import org.hibernate.sql.QueryJoinFragment;
import org.hibernate.type.AssociationType;

/**
* A sequence of {@link Join} delegates to make it "easier" to work with joins.  The "easier" part is obviously
* subjective ;)
* <p/>
* Additionally JoinSequence is a directed graph of other JoinSequence instances, as represented by the
* {@link #next} ({@link #setNext(JoinSequence)}) pointer.
*
* @author Gavin King
* @author Steve Ebersole
*
* @see JoinFragment
*/
public class JoinSequence {
  private final SessionFactoryImplementor factory;

  private final StringBuilder conditions = new StringBuilder();
  private final List<Join> joins = new ArrayList<Join>();

  private boolean useThetaStyle;
  private String rootAlias;
  private Joinable rootJoinable;
  private Selector selector;
  private JoinSequence next;
  private boolean isFromPart;

  /**
   * Constructs a JoinSequence
   *
   * @param factory The SessionFactory
   */
  public JoinSequence(SessionFactoryImplementor factory) {
    this.factory = factory;
  }

  /**
   * Retrieve a JoinSequence that represents just the FROM clause parts
   *
   * @return The JoinSequence that represents just the FROM clause parts
   */
  public JoinSequence getFromPart() {
    final JoinSequence fromPart = new JoinSequence( factory );
    fromPart.joins.addAll( this.joins );
    fromPart.useThetaStyle = this.useThetaStyle;
    fromPart.rootAlias = this.rootAlias;
    fromPart.rootJoinable = this.rootJoinable;
    fromPart.selector = this.selector;
    fromPart.next = this.next == null ? null : this.next.getFromPart();
    fromPart.isFromPart = true;
    return fromPart;
  }

  private Set<String> treatAsDeclarations;

  public void applyTreatAsDeclarations(Set<String> treatAsDeclarations) {
    if ( treatAsDeclarations == null || treatAsDeclarations.isEmpty() ) {
      return;
    }

    if ( this.treatAsDeclarations == null ) {
      this.treatAsDeclarations = new HashSet<String>();
    }

    this.treatAsDeclarations.addAll( treatAsDeclarations );
  }


  /**
   * Create a full, although shallow, copy.
   *
   * @return The copy
   */
  public JoinSequence copy() {
    final JoinSequence copy = new JoinSequence( factory );
    copy.joins.addAll( this.joins );
    copy.useThetaStyle = this.useThetaStyle;
    copy.rootAlias = this.rootAlias;
    copy.rootJoinable = this.rootJoinable;
    copy.selector = this.selector;
    copy.next = this.next == null ? null : this.next.copy();
    copy.isFromPart = this.isFromPart;
    copy.conditions.append( this.conditions.toString() );
    return copy;
  }

  /**
   * Add a join to this sequence
   *
   * @param associationType The type of the association representing the join
   * @param alias The RHS alias for the join
   * @param joinType The type of join (INNER, etc)
   * @param referencingKey The LHS columns for the join condition
   *
   * @return The Join memento
   *
   * @throws MappingException Generally indicates a problem resolving the associationType to a {@link Joinable}
   */
  public JoinSequence addJoin(
      AssociationType associationType,
      String alias,
      JoinType joinType,
      String[] referencingKey) throws MappingException {
    joins.add( new Join( factory, associationType, alias, joinType, referencingKey ) );
    return this;
  }

  /**
   * Generate a JoinFragment
   *
   * @return The JoinFragment
   *
   * @throws MappingException Indicates a problem access the provided metadata, or incorrect metadata
   */
  public JoinFragment toJoinFragment() throws MappingException {
    return toJoinFragment( Collections.EMPTY_MAP, true );
  }

  /**
   * Generate a JoinFragment
   *
   * @param enabledFilters The filters associated with the originating session to properly define join conditions
   * @param includeAllSubclassJoins Should all subclass joins be added to the rendered JoinFragment?
   *
   * @return The JoinFragment
   *
   * @throws MappingException Indicates a problem access the provided metadata, or incorrect metadata
   */
  public JoinFragment toJoinFragment(Map enabledFilters, boolean includeAllSubclassJoins) throws MappingException {
    return toJoinFragment( enabledFilters, includeAllSubclassJoins, null, null );
  }

  /**
   * Generate a JoinFragment
   *
   * @param enabledFilters The filters associated with the originating session to properly define join conditions
   * @param includeAllSubclassJoins Should all subclass joins be added to the rendered JoinFragment?
   * @param withClauseFragment The with clause (which represents additional join restrictions) fragment
   * @param withClauseJoinAlias The
   *
   * @return The JoinFragment
   *
   * @throws MappingException Indicates a problem access the provided metadata, or incorrect metadata
   */
  public JoinFragment toJoinFragment(
      Map enabledFilters,
      boolean includeAllSubclassJoins,
      String withClauseFragment,
      String withClauseJoinAlias) throws MappingException {
    final QueryJoinFragment joinFragment = new QueryJoinFragment( factory.getDialect(), useThetaStyle );
    if ( rootJoinable != null ) {
      joinFragment.addCrossJoin( rootJoinable.getTableName(), rootAlias );
      final String filterCondition = rootJoinable.filterFragment( rootAlias, enabledFilters, treatAsDeclarations );
      // JoinProcessor needs to know if the where clause fragment came from a dynamic filter or not so it
      // can put the where clause fragment in the right place in the SQL AST.   'hasFilterCondition' keeps track
      // of that fact.
      joinFragment.setHasFilterCondition( joinFragment.addCondition( filterCondition ) );
      addSubclassJoins( joinFragment, rootAlias, rootJoinable, true, includeAllSubclassJoins, treatAsDeclarations );
    }

    Joinable last = rootJoinable;

    for ( Join join : joins ) {
      // technically the treatAsDeclarations should only apply to rootJoinable or to a single Join,
      // but that is not possible atm given how these JoinSequence and Join objects are built.
      // However, it is generally ok given how the HQL parser builds these JoinSequences (a HQL join
      // results in a JoinSequence with an empty rootJoinable and a single Join).  So we use that here
      // as an assumption
      final String on = join.getAssociationType().getOnCondition( join.getAlias(), factory, enabledFilters, treatAsDeclarations );
      String condition;
      if ( last != null
          && isManyToManyRoot( last )
          && ((QueryableCollection) last).getElementType() == join.getAssociationType() ) {
        // the current join represents the join between a many-to-many association table
        // and its "target" table.  Here we need to apply any additional filters
        // defined specifically on the many-to-many
        final String manyToManyFilter = ( (QueryableCollection) last ).getManyToManyFilterFragment(
            join.getAlias(),
            enabledFilters
        );
        condition = "".equals( manyToManyFilter )
            ? on
            : "".equals( on ) ? manyToManyFilter : on + " and " + manyToManyFilter;
      }
      else {
        condition = on;
      }

      if ( withClauseFragment != null && !isManyToManyRoot( join.joinable )) {
        condition += " and " + withClauseFragment;
      }

      joinFragment.addJoin(
          join.getJoinable().getTableName(),
          join.getAlias(),
          join.getLHSColumns(),
          JoinHelper.getRHSColumnNames( join.getAssociationType(), factory ),
          join.joinType,
          condition
      );

      addSubclassJoins(
          joinFragment,
          join.getAlias(),
          join.getJoinable(),
          join.joinType == JoinType.INNER_JOIN,
          includeAllSubclassJoins,
          // ugh.. this is needed because of how HQL parser (FromElementFactory/SessionFactoryHelper)
          // builds the JoinSequence for HQL joins
          treatAsDeclarations
      );
      last = join.getJoinable();
    }

    if ( next != null ) {
      joinFragment.addFragment( next.toJoinFragment( enabledFilters, includeAllSubclassJoins ) );
    }

    joinFragment.addCondition( conditions.toString() );

    if ( isFromPart ) {
      joinFragment.clearWherePart();
    }

    return joinFragment;
  }

  @SuppressWarnings("SimplifiableIfStatement")
  private boolean isManyToManyRoot(Joinable joinable) {
    if ( joinable != null && joinable.isCollection() ) {
      return ( (QueryableCollection) joinable ).isManyToMany();
    }
    return false;
  }

  private void addSubclassJoins(
      JoinFragment joinFragment,
      String alias,
      Joinable joinable,
      boolean innerJoin,
      boolean includeSubclassJoins,
      Set<String> treatAsDeclarations) {
    final boolean include = includeSubclassJoins && isIncluded( alias );
    joinFragment.addJoins(
        joinable.fromJoinFragment( alias, innerJoin, include, treatAsDeclarations ),
        joinable.whereJoinFragment( alias, innerJoin, include, treatAsDeclarations )
    );
  }

  private boolean isIncluded(String alias) {
    return selector != null && selector.includeSubclasses( alias );
  }

  /**
   * Add a condition to this sequence.
   *
   * @param condition The condition
   *
   * @return {@link this}, for method chaining
   */
  public JoinSequence addCondition(String condition) {
    if ( condition.trim().length() != 0 ) {
      if ( !condition.startsWith( " and " ) ) {
        conditions.append( " and " );
      }
      conditions.append( condition );
    }
    return this;
  }

  /**
   * Add a condition to this sequence.  Typical usage here might be:
   * <pre>
   *     addCondition( "a", {"c1", "c2"}, "?" )
   * </pre>
   * to represent:
   * <pre>
   *     "... a.c1 = ? and a.c2 = ? ..."
   * </pre>
   *
   * @param alias The alias to apply to the columns
   * @param columns The columns to add checks for
   * @param condition The conditions to check against the columns
   *
   * @return {@link this}, for method chaining
   */
  public JoinSequence addCondition(String alias, String[] columns, String condition) {
    for ( String column : columns ) {
      conditions.append( " and " )
          .append( alias )
          .append( '.' )
          .append( column )
          .append( condition );
    }
    return this;
  }

  /**
   * Set the root of this JoinSequence.  In SQL terms, this would be the driving table.
   *
   * @param joinable The entity/collection that is the root of this JoinSequence
   * @param alias The alias associated with that joinable.
   *
   * @return {@link this}, for method chaining
   */
  public JoinSequence setRoot(Joinable joinable, String alias) {
    this.rootAlias = alias;
    this.rootJoinable = joinable;
    return this;
  }

  /**
   * Sets the next join sequence
   *
   * @param next The next JoinSequence in the directed graph
   *
   * @return {@code this}, for method chaining
   */
  public JoinSequence setNext(JoinSequence next) {
    this.next = next;
    return this;
  }

  /**
   * Set the Selector to use to determine how subclass joins should be applied.
   *
   * @param selector The selector to apply
   *
   * @return {@code this}, for method chaining
   */
  public JoinSequence setSelector(Selector selector) {
    this.selector = selector;
    return this;
  }

  /**
   * Should this JoinSequence use theta-style joining (both a FROM and WHERE component) in the rendered SQL?
   *
   * @param useThetaStyle {@code true} indicates that theta-style joins should be used.
   *
   * @return {@code this}, for method chaining
   */
  public JoinSequence setUseThetaStyle(boolean useThetaStyle) {
    this.useThetaStyle = useThetaStyle;
    return this;
  }

  public boolean isThetaStyle() {
    return useThetaStyle;
  }

  public Join getFirstJoin() {
    return joins.get( 0 );
  }

  /**
   * A subclass join selector
   */
  public static interface Selector {
    /**
     * Should subclasses be included in the rendered join sequence?
     *
     * @param alias The alias
     *
     * @return {@code true} if the subclass joins should be included
     */
    public boolean includeSubclasses(String alias);
  }

  /**
   * Represents a join
   */
  public static final class Join {
    private final AssociationType associationType;
    private final Joinable joinable;
    private final JoinType joinType;
    private final String alias;
    private final String[] lhsColumns;

    Join(
        SessionFactoryImplementor factory,
        AssociationType associationType,
        String alias,
        JoinType joinType,
        String[] lhsColumns) throws MappingException {
      this.associationType = associationType;
      this.joinable = associationType.getAssociatedJoinable( factory );
      this.alias = alias;
      this.joinType = joinType;
      this.lhsColumns = lhsColumns;
    }

    public String getAlias() {
      return alias;
    }

    public AssociationType getAssociationType() {
      return associationType;
    }

    public Joinable getJoinable() {
      return joinable;
    }

    public JoinType getJoinType() {
      return joinType;
    }

    public String[] getLHSColumns() {
      return lhsColumns;
    }

    @Override
    public String toString() {
      return joinable.toString() + '[' + alias + ']';
    }
  }

  @Override
  public String toString() {
    final StringBuilder buf = new StringBuilder();
    buf.append( "JoinSequence{" );
    if ( rootJoinable != null ) {
      buf.append( rootJoinable )
          .append( '[' )
          .append( rootAlias )
          .append( ']' );
    }
    for ( Join join : joins ) {
      buf.append( "->" ).append( join );
    }
    return buf.append( '}' ).toString();
  }
}
TOP

Related Classes of org.hibernate.engine.internal.JoinSequence

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.