/*
 * 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 Join delegates to make it "easier" to work with joins. The "easier" part is obviously subjective ;)

Additionally JoinSequence is a directed graph of other JoinSequence instances, as represented by the next (setNext(JoinSequence)) pointer.
Author:Gavin King, Steve Ebersole
See Also:
/** * 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
Params:
  • factory – The SessionFactory
/** * Constructs a JoinSequence * * @param factory The SessionFactory */
public JoinSequence(SessionFactoryImplementor factory) { this.factory = factory; }
Retrieve a JoinSequence that represents just the FROM clause parts
Returns:The JoinSequence that represents just the FROM clause parts
/** * 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.
Returns:The copy
/** * 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
Params:
  • associationType – The type of the association representing the join
  • alias – The RHS alias for the join
  • joinType – The type of join (INNER, etc)
  • referencingKey – The LHS columns for the join condition
Throws:
Returns:The Join memento
/** * 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
Throws:
  • MappingException – Indicates a problem access the provided metadata, or incorrect metadata
Returns:The JoinFragment
/** * 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
Params:
  • enabledFilters – The filters associated with the originating session to properly define join conditions
  • includeAllSubclassJoins – Should all subclass joins be added to the rendered JoinFragment?
Throws:
  • MappingException – Indicates a problem access the provided metadata, or incorrect metadata
Returns:The JoinFragment
/** * 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
Params:
  • enabledFilters – The filters associated with the originating session to properly define join conditions
  • includeAllSubclassJoins – Should all subclass joins be added to the rendered JoinFragment?
  • withClauseFragment – The with clause (which represents additional join restrictions) fragment
  • withClauseJoinAlias – The
Throws:
  • MappingException – Indicates a problem access the provided metadata, or incorrect metadata
Returns:The JoinFragment
/** * 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.
Params:
  • condition – The condition
Returns:this, for method chaining
/** * 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:
    addCondition( "a", {"c1", "c2"}, "?" )
to represent:
    "... a.c1 = ? and a.c2 = ? ..."
Params:
  • alias – The alias to apply to the columns
  • columns – The columns to add checks for
  • condition – The conditions to check against the columns
Returns:this, for method chaining
/** * 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.
Params:
  • joinable – The entity/collection that is the root of this JoinSequence
  • alias – The alias associated with that joinable.
Returns:this, for method chaining
/** * 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
Params:
  • next – The next JoinSequence in the directed graph
Returns:this, for method chaining
/** * 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.
Params:
  • selector – The selector to apply
Returns:this, for method chaining
/** * 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?
Params:
  • useThetaStyle – true indicates that theta-style joins should be used.
Returns:this, for method chaining
/** * 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
/** * A subclass join selector */
public static interface Selector {
Should subclasses be included in the rendered join sequence?
Params:
  • alias – The alias
Returns:true if the subclass joins should be included
/** * 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
/** * 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(); } }