package org.hibernate.engine.internal;
import java.util.*;
import java.util.Collections;
import org.hibernate.AssertionFailure;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.internal.ast.tree.ImpliedFromElement;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.AbstractEntityPersister;
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;
public class JoinSequence {
private final SessionFactoryImplementor factory;
private final boolean collectionJoinSubquery;
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;
public JoinSequence(SessionFactoryImplementor factory) {
this.factory = factory;
this.collectionJoinSubquery = factory.getSessionFactoryOptions().isCollectionJoinSubqueryRewriteEnabled();
}
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 );
}
protected Set<String> getTreatAsDeclarations() {
return treatAsDeclarations;
}
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;
}
public JoinSequence addJoin(
AssociationType associationType,
String alias,
JoinType joinType,
String[] referencingKey) throws MappingException {
joins.add( new Join( factory, associationType, alias, joinType, new String[][] { referencingKey } ) );
return this;
}
public JoinSequence addJoin(
AssociationType associationType,
String alias,
JoinType joinType,
String[][] referencingKeys) throws MappingException {
joins.add( new Join( factory, associationType, alias, joinType, referencingKeys ) );
return this;
}
public JoinSequence addJoin(ImpliedFromElement fromElement) {
joins.addAll( fromElement.getJoinSequence().joins );
return this;
}
public JoinFragment toJoinFragment() throws MappingException {
return toJoinFragment( Collections.EMPTY_MAP, true );
}
public JoinFragment toJoinFragment(Map enabledFilters, boolean includeAllSubclassJoins) throws MappingException {
return toJoinFragment( enabledFilters, includeAllSubclassJoins, null );
}
public JoinFragment toJoinFragment(
Map enabledFilters,
boolean includeAllSubclassJoins,
String withClauseFragment) throws MappingException {
return toJoinFragment( enabledFilters, includeAllSubclassJoins, true, withClauseFragment );
}
public JoinFragment toJoinFragment(
Map enabledFilters,
boolean includeAllSubclassJoins,
boolean renderSubclassJoins,
String withClauseFragment) throws MappingException {
final QueryJoinFragment joinFragment = new QueryJoinFragment( factory.getDialect(), useThetaStyle );
Iterator<Join> iter;
Join first;
Joinable last;
if ( rootJoinable != null ) {
joinFragment.addCrossJoin( rootJoinable.getTableName(), rootAlias );
final String filterCondition = rootJoinable.filterFragment( rootAlias, enabledFilters, treatAsDeclarations );
joinFragment.setHasFilterCondition( joinFragment.addCondition( filterCondition ) );
addSubclassJoins( joinFragment, rootAlias, rootJoinable, true, includeAllSubclassJoins, treatAsDeclarations );
last = rootJoinable;
}
else if ( needsTableGroupJoin( joins, withClauseFragment ) ) {
iter = joins.iterator();
first = iter.next();
final String joinString;
switch (first.joinType) {
case INNER_JOIN:
joinString = " inner join ";
break;
case LEFT_OUTER_JOIN:
joinString = " left outer join ";
break;
case RIGHT_OUTER_JOIN:
joinString = " right outer join ";
break;
case FULL_JOIN:
joinString = " full outer join ";
break;
default:
throw new AssertionFailure("undefined join type");
}
joinFragment.addFromFragmentString( joinString );
joinFragment.addFromFragmentString( " (" );
joinFragment.addFromFragmentString( first.joinable.getTableName() );
joinFragment.addFromFragmentString( " " );
joinFragment.addFromFragmentString( first.getAlias() );
for ( Join join : joins ) {
if ( join != first ) {
joinFragment.addJoin(
join.getJoinable().getTableName(),
join.getAlias(),
join.getLHSColumns(),
JoinHelper.getRHSColumnNames( join.getAssociationType(), factory ),
join.joinType
);
}
addSubclassJoins(
joinFragment,
join.getAlias(),
join.getJoinable(),
join.joinType == JoinType.INNER_JOIN,
includeAllSubclassJoins,
treatAsDeclarations
);
}
joinFragment.addFromFragmentString( ")" );
joinFragment.addFromFragmentString( " on " );
final String rhsAlias = first.getAlias();
final String[][] lhsColumns = first.getLHSColumns();
final String[] rhsColumns = JoinHelper.getRHSColumnNames( first.getAssociationType(), factory );
if ( lhsColumns.length > 1 ) {
joinFragment.addFromFragmentString( "(" );
}
for ( int i = 0; i < lhsColumns.length; i++ ) {
for ( int j = 0; j < lhsColumns[i].length; j++ ) {
joinFragment.addFromFragmentString( lhsColumns[i][j] );
joinFragment.addFromFragmentString( "=" );
joinFragment.addFromFragmentString( rhsAlias );
joinFragment.addFromFragmentString( "." );
joinFragment.addFromFragmentString( rhsColumns[j] );
if ( j < lhsColumns[i].length - 1 ) {
joinFragment.addFromFragmentString( " and " );
}
}
if ( i < lhsColumns.length - 1 ) {
joinFragment.addFromFragmentString( " or " );
}
}
if ( lhsColumns.length > 1 ) {
joinFragment.addFromFragmentString( ")" );
}
joinFragment.addFromFragmentString( " and " );
joinFragment.addFromFragmentString( withClauseFragment );
return joinFragment;
}
else {
last = null;
}
for ( Join join : joins ) {
final String on = join.getAssociationType().getOnCondition( join.getAlias(), factory, enabledFilters, treatAsDeclarations );
String condition;
if ( last != null
&& isManyToManyRoot( last )
&& ((QueryableCollection) last).getElementType() == join.getAssociationType() ) {
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
);
if (renderSubclassJoins) {
addSubclassJoins(
joinFragment,
join.getAlias(),
join.getJoinable(),
join.joinType == JoinType.INNER_JOIN,
includeAllSubclassJoins,
treatAsDeclarations
);
}
last = join.getJoinable();
}
if ( next != null ) {
joinFragment.addFragment( next.toJoinFragment( enabledFilters, includeAllSubclassJoins ) );
}
joinFragment.addCondition( conditions.toString() );
if ( isFromPart ) {
joinFragment.clearWherePart();
}
return joinFragment;
}
private boolean needsTableGroupJoin(List<Join> joins, String withClauseFragment) {
if ( !collectionJoinSubquery || StringHelper.isEmpty( withClauseFragment ) ) {
return false;
}
if ( joins.size() < 2 ) {
return isSubclassAliasDereferenced( joins.get( 0 ), withClauseFragment );
}
if ( joins.get( 0 ).getJoinType() != JoinType.INNER_JOIN ) {
return true;
}
if ( isSubclassAliasDereferenced( joins.get( 0 ), withClauseFragment ) ) {
return true;
}
for ( int i = 1; i < joins.size(); i++ ) {
Join join = joins.get( i );
if ( isAliasDereferenced( withClauseFragment, join.getAlias() ) || isSubclassAliasDereferenced( join, withClauseFragment ) ) {
return true;
}
}
return false;
}
private boolean isSubclassAliasDereferenced(Join join, String withClauseFragment) {
if ( join.getJoinable() instanceof AbstractEntityPersister ) {
AbstractEntityPersister persister = (AbstractEntityPersister) join.getJoinable();
int subclassTableSpan = persister.getSubclassTableSpan();
for ( int j = 1; j < subclassTableSpan; j++ ) {
String subclassAlias = AbstractEntityPersister.generateTableAlias( join.getAlias(), j );
if ( isAliasDereferenced( withClauseFragment, subclassAlias ) ) {
return true;
}
}
}
return false;
}
private boolean isAliasDereferenced(String withClauseFragment, String alias) {
int index = withClauseFragment.indexOf( alias );
int dotIndex = index + alias.length();
if ( index != -1
&& ( index == 0 || !Character.isLetterOrDigit( withClauseFragment.charAt( index - 1 ) ) )
&& dotIndex < withClauseFragment.length() && withClauseFragment.charAt( dotIndex ) == '.' ) {
return true;
}
return false;
}
@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 )
);
}
protected boolean isIncluded(String alias) {
return selector != null && selector.includeSubclasses( alias );
}
public JoinSequence addCondition(String condition) {
if ( condition.trim().length() != 0 ) {
if ( !condition.startsWith( " and " ) ) {
conditions.append( " and " );
}
conditions.append( condition );
}
return this;
}
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;
}
public JoinSequence setRoot(Joinable joinable, String alias) {
this.rootAlias = alias;
this.rootJoinable = joinable;
return this;
}
public JoinSequence setNext(JoinSequence next) {
this.next = next;
return this;
}
public JoinSequence setSelector(Selector selector) {
this.selector = selector;
return this;
}
public JoinSequence setUseThetaStyle(boolean useThetaStyle) {
this.useThetaStyle = useThetaStyle;
return this;
}
public boolean isThetaStyle() {
return useThetaStyle;
}
public Join getFirstJoin() {
return joins.get( 0 );
}
public static interface Selector {
public boolean includeSubclasses(String alias);
}
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 + ']';
}
}
public JoinSequence copyForCollectionProperty() {
JoinSequence copy = this.copy();
copy.joins.clear();
Iterator<Join> joinIterator = this.joins.iterator();
while ( joinIterator.hasNext() ) {
Join join = joinIterator.next();
copy.addJoin(
join.getAssociationType(),
join.getAlias(),
JoinType.INNER_JOIN,
join.getLHSColumns()
);
}
return copy;
}
@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();
}
}