All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.hql.ast.spi.SingleEntityQueryRendererDelegate Maven / Gradle / Ivy

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * JBoss, Home of Professional Open Source
 * Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the @authors tag. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * 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, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * 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,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */
package org.hibernate.hql.ast.spi;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.antlr.runtime.tree.Tree;
import org.hibernate.hql.ast.common.JoinType;
import org.hibernate.hql.ast.origin.hql.resolve.path.AggregationPropertyPath;
import org.hibernate.hql.ast.origin.hql.resolve.path.PropertyPath;
import org.hibernate.hql.ast.spi.predicate.ComparisonPredicate.Type;
import org.hibernate.hql.internal.logging.Log;
import org.hibernate.hql.internal.logging.LoggerFactory;

/**
 * This extends the ANTLR generated AST walker to transform a parsed tree
 * into a query object and collect the target entity types of the query.
 * 
* TODO: *
  • It is currently human-written but should evolve into another ANTLR * generated tree walker, not extending GeneratedHQLResolver but using its * output as a generic normalization AST transformer.
  • *
  • We are assembling the query directly, but this doesn't take * into account parameter types which might need some transformation; * the Hibernate Search provided {@code QueryBuilder} could do this.
  • *
  • Implement more predicates
  • *
  • Support multiple types being targeted by the Query
  • *
  • Support positional parameters (currently only consumed named parameters)
  • * * @author Sanne Grinovero (C) 2011 Red Hat Inc. * @author Gunnar Morling * @author Adrian Nistor */ public abstract class SingleEntityQueryRendererDelegate implements QueryRendererDelegate { protected static final String SORT_ASC_SPEC = "asc"; private static final Log log = LoggerFactory.make(); /** * States which this object can have during tree walking * * @author Gunnar Morling */ protected enum Status { DEFINING_SELECT, DEFINING_FROM, DEFINING_WHERE, DEFINING_GROUP_BY, DEFINING_HAVING, DEFINING_ORDER_BY } /** * The current status */ protected Status status; protected String targetTypeName; protected Class targetType; protected PropertyPath propertyPath; protected AggregationPropertyPath.Type aggregationType; protected final SingleEntityQueryBuilder builder; protected final List projections = new ArrayList(); /** * Persister space: keep track of aliases and entity names. */ protected final Map aliasToEntityType = new HashMap(); protected final Map aliasToPropertyPath = new HashMap(); protected String alias; private final Map namedParameters; /** * How to resolve entity names to class instances */ private final EntityNamesResolver entityNames; private final PropertyHelper propertyHelper; public SingleEntityQueryRendererDelegate(PropertyHelper propertyHelper, EntityNamesResolver entityNames, SingleEntityQueryBuilder builder, Map namedParameters) { this.propertyHelper = propertyHelper; this.entityNames = entityNames; this.namedParameters = namedParameters != null ? namedParameters : Collections.emptyMap(); this.builder = builder; } /** * Return the optional HAVING clause builder. To be overridden by subclasses that wish to support the HAVING clause. */ protected SingleEntityHavingQueryBuilder getHavingBuilder() { return null; } /** * See rule entityName */ @Override public void registerPersisterSpace(Tree entityName, Tree alias) { registerAlias( entityName.getText(), alias.getText() ); setTargetTypeName( entityName.getText() ); setTargetType( entityName.getText() ); builder.setEntityType( targetTypeName ); if ( getHavingBuilder() != null ) { getHavingBuilder().setEntityType( targetTypeName ); } } private void registerAlias(String entityName, String alias) { String put = aliasToEntityType.put( alias, entityName ); if ( put != null && !put.equalsIgnoreCase( entityName ) ) { throw new UnsupportedOperationException( "Alias reuse currently not supported: alias " + alias + " already assigned to type " + put ); } } public void registerEmbeddedAlias(String alias, PropertyPath propertyPath) { PropertyPath put = aliasToPropertyPath.put( alias, propertyPath ); if ( put != null ) { throw new UnsupportedOperationException( "Alias reuse currently not supported: alias " + alias + " already assigned to type " + put ); } } private void setTargetType(String entityName) { Class targetedType = entityNames.getClassFromName( entityName ); if ( targetedType == null ) { throw new IllegalStateException( "Unknown entity name " + entityName ); } targetType = targetedType; } private void setTargetTypeName(String entityName) { if ( targetTypeName != null ) { throw new IllegalStateException( "Can't target multiple types: " + targetTypeName + " already selected before " + entityName ); } targetTypeName = entityName; } @Override public boolean isUnqualifiedPropertyReference() { return true; } @Override public boolean isPersisterReferenceAlias() { return aliasToEntityType.containsKey( alias ); } @Override public void pushFromStrategy( JoinType joinType, Tree assosiationFetchTree, Tree propertyFetchTree, Tree alias) { status = Status.DEFINING_FROM; this.alias = alias.getText(); } @Override public void pushSelectStrategy() { status = Status.DEFINING_SELECT; } @Override public void pushWhereStrategy() { status = Status.DEFINING_WHERE; } @Override public void pushGroupByStrategy() { status = Status.DEFINING_GROUP_BY; if ( getHavingBuilder() == null ) { throw new UnsupportedOperationException( "The GROUP BY clause is not supported" ); } } @Override public void pushHavingStrategy() { status = Status.DEFINING_HAVING; if ( getHavingBuilder() == null ) { throw new UnsupportedOperationException( "The HAVING clause is not supported" ); } } @Override public void pushOrderByStrategy() { status = Status.DEFINING_ORDER_BY; } @Override public void popStrategy() { status = null; alias = null; propertyPath = null; aggregationType = null; } @Override public void activateOR() { if ( status == Status.DEFINING_WHERE ) { builder.pushOrPredicate(); } else if ( status == Status.DEFINING_HAVING ) { getHavingBuilder().pushOrPredicate(); } else { throw new IllegalStateException(); } } @Override public void activateAND() { if ( status == Status.DEFINING_WHERE ) { builder.pushAndPredicate(); } else if ( status == Status.DEFINING_HAVING ) { getHavingBuilder().pushAndPredicate(); } else { throw new IllegalStateException(); } } @Override public void activateNOT() { if ( status == Status.DEFINING_WHERE ) { builder.pushNotPredicate(); } else if ( status == Status.DEFINING_HAVING ) { getHavingBuilder().pushNotPredicate(); } else { throw new IllegalStateException(); } } @Override public void predicateLess(String comparativePredicate) { addComparisonPredicate( comparativePredicate, Type.LESS ); } @Override public void predicateLessOrEqual(String comparativePredicate) { addComparisonPredicate( comparativePredicate, Type.LESS_OR_EQUAL ); } /** * This implements the equality predicate; the comparison * predicate could be a constant, a subfunction or * some random type parameter. * The tree node has all details but with current tree rendering * it just passes it's text so we have to figure out the options again. */ @Override public void predicateEquals(final String comparativePredicate) { addComparisonPredicate( comparativePredicate, Type.EQUALS ); } @Override public void predicateNotEquals(String comparativePredicate) { activateNOT(); addComparisonPredicate( comparativePredicate, Type.EQUALS ); deactivateBoolean(); } @Override public void predicateGreaterOrEqual(String comparativePredicate) { addComparisonPredicate( comparativePredicate, Type.GREATER_OR_EQUAL ); } @Override public void predicateGreater(String comparativePredicate) { addComparisonPredicate( comparativePredicate, Type.GREATER ); } private void addComparisonPredicate(String comparativePredicate, Type comparisonType) { Object comparisonValue = parameterValue( comparativePredicate ); List property = resolveAlias( propertyPath ); if ( status == Status.DEFINING_WHERE ) { builder.addComparisonPredicate( property, comparisonType, comparisonValue ); } else if ( status == Status.DEFINING_HAVING ) { getHavingBuilder().addComparisonPredicate( getAggregation(), property, comparisonType, comparisonValue ); } else { throw new IllegalStateException(); } } @Override public void predicateIn(List list) { List values = fromNamedQuery( list ); List property = resolveAlias( propertyPath ); if ( status == Status.DEFINING_WHERE ) { builder.addInPredicate( property, values ); } else if ( status == Status.DEFINING_HAVING ) { getHavingBuilder().addInPredicate( getAggregation(), property, values ); } else { throw new IllegalStateException(); } } @Override public void predicateBetween(String lower, String upper) { Object lowerComparisonValue = parameterValue( lower ); Object upperComparisonValue = parameterValue( upper ); List property = resolveAlias( propertyPath ); if ( status == Status.DEFINING_WHERE ) { builder.addRangePredicate( property, lowerComparisonValue, upperComparisonValue ); } else if ( status == Status.DEFINING_HAVING ) { getHavingBuilder().addRangePredicate( getAggregation(), property, lowerComparisonValue, upperComparisonValue ); } else { throw new IllegalStateException(); } } @Override public void predicateLike(String patternValue, Character escapeCharacter) { Object pattern = parameterValue( patternValue ); List property = resolveAlias( propertyPath ); if ( status == Status.DEFINING_WHERE ) { builder.addLikePredicate( property, (String) pattern, escapeCharacter ); } else if ( status == Status.DEFINING_HAVING ) { getHavingBuilder().addLikePredicate( getAggregation(), property, (String) pattern, escapeCharacter ); } else { throw new IllegalStateException(); } } @Override public void predicateIsNull() { List property = resolveAlias( propertyPath ); if ( status == Status.DEFINING_WHERE ) { builder.addIsNullPredicate( property ); } else if ( status == Status.DEFINING_HAVING ) { getHavingBuilder().addIsNullPredicate( getAggregation(), property ); } else { throw new IllegalStateException(); } } @Override public void setPropertyReferencePath(PropertyPath propertyPath) { if (aggregationType != null) { if ( propertyPath == null && aggregationType != AggregationPropertyPath.Type.COUNT && aggregationType != AggregationPropertyPath.Type.COUNT_DISTINCT ) { throw log.getAggregationCanOnlyBeAppliedToPropertyReferencesException( aggregationType.name() ); } propertyPath = new AggregationPropertyPath( aggregationType, propertyPath ); } setPropertyPath( propertyPath ); } @Override public void activateAggregation(AggregationPropertyPath.Type aggregationType) { if ( status == Status.DEFINING_WHERE ) { throw log.getNoAggregationsInWhereClauseException( aggregationType.name() ); } if ( status == Status.DEFINING_GROUP_BY ) { throw log.getNoAggregationsInGroupByClauseException( aggregationType.name() ); } this.aggregationType = aggregationType; propertyPath = null; } @Override public void deactivateAggregation() { aggregationType = null; } private AggregationPropertyPath.Type getAggregation() { if ( propertyPath instanceof AggregationPropertyPath ) { return ((AggregationPropertyPath) propertyPath).getType(); } return null; } @Override public void sortSpecification(String collateName, String orderSpec) { // orderSpec is already normalized to be lowercase and non-null addSortField( propertyPath, collateName, SORT_ASC_SPEC.equals( orderSpec ) ); } @Override public void groupingValue(String collateName) { addGrouping( propertyPath, collateName ); } /** * Add field sort criteria. To be overridden by subclasses. * * @param propertyPath the path of the field being sorted * @param collateName optional collation name * @param isAscending sort direction */ protected void addSortField(PropertyPath propertyPath, String collateName, boolean isAscending) { throw new UnsupportedOperationException( "Sorting is not supported : " + propertyPath.asStringPathWithoutAlias() ); } /** * Add 'group by' criteria. To be overridden by subclasses. * * @param propertyPath the path of the field used for grouping * @param collateName optional collation name */ protected void addGrouping(PropertyPath propertyPath, String collateName) { throw new UnsupportedOperationException( "Grouping is not supported : " + propertyPath.asStringPathWithoutAlias() ); } private Object parameterValue(String comparativePredicate) { // It's a named parameter; Value given via setParameter(), taking that as is if ( comparativePredicate.startsWith( ":" ) ) { return namedParameters.get( comparativePredicate.substring( 1 ) ); } // It's a value given in JP-QL; Convert the literal value else { List path = new ArrayList(); path.addAll( propertyPath.getNodeNamesWithoutAlias() ); PropertyPath fullPath = propertyPath; // create the complete path in case it's a join while ( fullPath.getFirstNode().isAlias() && aliasToPropertyPath.containsKey( fullPath.getFirstNode().getName() ) ) { fullPath = aliasToPropertyPath.get( fullPath.getFirstNode().getName() ); path.addAll( 0, fullPath.getNodeNamesWithoutAlias() ); } return propertyHelper.convertToPropertyType( targetTypeName, path, comparativePredicate ); } } private List fromNamedQuery(List list) { List elements = new ArrayList( list.size() ); for ( String string : list ) { elements.add( parameterValue( string ) ); } return elements; } @Override public void deactivateBoolean() { if ( status == Status.DEFINING_WHERE ) { builder.popBooleanPredicate(); } else if ( status == Status.DEFINING_HAVING ) { getHavingBuilder().popBooleanPredicate(); } else { throw new IllegalStateException(); } } @Override public abstract R getResult(); protected List resolveAlias(PropertyPath path) { if ( path.getFirstNode().isAlias() ) { String alias = path.getFirstNode().getName(); if ( aliasToEntityType.containsKey( alias ) ) { // Alias for entity return path.getNodeNamesWithoutAlias(); } else if ( aliasToPropertyPath.containsKey( alias ) ) { // Alias for embedded PropertyPath propertyPath = aliasToPropertyPath.get( alias ); List resolvedAlias = resolveAlias( propertyPath ); resolvedAlias.addAll( path.getNodeNamesWithoutAlias() ); return resolvedAlias; } else { // Alias not found aliasNotFound( alias ); } } // It does not start with an alias return path.getNodeNamesWithoutAlias(); } @Override public void registerJoinAlias(Tree alias, PropertyPath path) { if ( !aliasToPropertyPath.containsKey( alias.getText() ) ) { aliasToPropertyPath.put( alias.getText(), path ); } } /** * What to do when a mentioned alias is not found. * An example usage is to throw a custom exception. * * @param alias the name of the alias which wasn't recognised */ public void aliasNotFound(String alias) { throw log.getUnknownAliasException( alias ); } }