Package org.hibernate.ogm.query.impl

Source Code of org.hibernate.ogm.query.impl.OgmQueryTranslator$CacheKey

/*
* Hibernate OGM, Domain model persistence for NoSQL datastores
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.ogm.query.impl;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;

import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.ScrollableResults;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.event.spi.EventSource;
import org.hibernate.hql.internal.ast.HqlParser;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.internal.ast.QueryTranslatorImpl.JavaConstantConverter;
import org.hibernate.hql.internal.ast.tree.SelectClause;
import org.hibernate.hql.internal.ast.util.NodeTraverser;
import org.hibernate.hql.spi.QueryTranslator;
import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
import org.hibernate.loader.hql.QueryLoader;
import org.hibernate.ogm.dialect.query.spi.BackendQuery;
import org.hibernate.ogm.model.key.spi.EntityKeyMetadata;
import org.hibernate.ogm.persister.impl.OgmEntityPersister;
import org.hibernate.ogm.query.spi.QueryParserService;
import org.hibernate.ogm.query.spi.QueryParsingResult;
import org.hibernate.ogm.type.spi.GridType;
import org.hibernate.ogm.util.impl.Log;
import org.hibernate.ogm.util.impl.LoggerFactory;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;

import antlr.RecognitionException;
import antlr.TokenStreamException;
import antlr.collections.AST;

/**
* A {@link QueryTranslator} which converts JP-QL queries into store-dependent native queries, e.g. Cypher queries for
* Neo4j or {@code DBObject}-based queries for MongoDB.
* <p>
* Query conversion is done by invoking the dialect's query parser service. Results are loaded through OgmQueryLoader.
* Depending on whether a store supports parameterized queries (Neo4j does, MongoDB doesn't) we either use one and the
* same loader for a query executed several times with different parameter values or we create a new loader for each set
* of parameter values.
*
* @author Gunnar Morling
*/
public class OgmQueryTranslator extends LegacyParserBridgeQueryTranslator {

  private static final Log log = LoggerFactory.make();

  private final String query;
  private final SessionFactoryImplementor sessionFactory;
  private final Map<?, ?> filters;

  private final QueryParserService queryParser;

  /**
   * The query loader in case the dialect supports parameterized queries; We can re-execute it then with different
   * parameter values.
   */
  private OgmQueryLoader loader;

  /**
   * Needed to create query loaders. This won't be required anymore once {@link OgmQueryLoader} doesn't depend that
   * much on {@link QueryLoader}.
   */
  private SelectClause selectClause;

  private EntityKeyMetadata singleEntityKeyMetadata;

  /**
   * Not all stores support parameterized queries. As a temporary measure, we therefore cache created queries per set
   * of parameter values. At one point, this should be replaced by caching the AST after validation but before the
   * actual Lucene query is created.
   */
  private final ConcurrentMap<CacheKey, QueryParsingResult> queryCache;

  public OgmQueryTranslator(SessionFactoryImplementor sessionFactory, QueryParserService queryParser, String queryIdentifier, String query, Map<?, ?> filters) {
    super( sessionFactory, queryIdentifier, query, filters );

    this.queryParser = queryParser;
    this.query = query;
    this.sessionFactory = sessionFactory;
    this.filters = filters;

    queryCache = new BoundedConcurrentHashMap<CacheKey, QueryParsingResult>(
        100,
        20,
        BoundedConcurrentHashMap.Eviction.LIRS
    );
  }

  @Override
  protected void doCompile(Map replacements, boolean shallow) throws QueryException, MappingException {
    try {
      // Unfortunately, we cannot obtain the select clause from the delegate, so we need to parse it again
      selectClause = getSelectClause( replacements, null );
      Type[] queryReturnTypes = selectClause.getQueryReturnTypes();
      this.singleEntityKeyMetadata = getSingleEntityKeyMetadataOrNull( queryReturnTypes );
    }
    catch ( Exception qse ) {
      throw log.querySyntaxException( qse, query );
    }

    if ( queryParser.supportsParameters() ) {
      loader = getLoader( null );
    }
  }

  @Override
  public List<?> list(SessionImplementor session, QueryParameters queryParameters) throws HibernateException {
    OgmQueryLoader loaderToUse = loader != null ? loader : getLoader( queryParameters );
    return loaderToUse.list( session, queryParameters );
  }

  private <T> OgmQueryLoader getLoader(QueryParameters queryParameters) {
    QueryParsingResult queryParsingResult = queryParameters != null ?
        getQuery( queryParameters ) :
        queryParser.parseQuery( sessionFactory, query );

    BackendQuery<T> query = new BackendQuery<T>( (T) queryParsingResult.getQueryObject(), singleEntityKeyMetadata );

    return new OgmQueryLoader( delegate, sessionFactory, selectClause, query, queryParsingResult.getColumnNames() );
  }

  /**
   * Returns the {@link EntityKeyMetadata} of the entity type selected by this query.
   * @param queryReturnTypes
   *
   * @return the {@link EntityKeyMetadata} of the entity type selected by this query or {@code null} in case this
   * query does not select exactly one entity type (e.g. in case of scalar values or joins (if supported in future revisions)).
   */
  private EntityKeyMetadata getSingleEntityKeyMetadataOrNull(Type[] queryReturnTypes) {
    EntityKeyMetadata metadata = null;

    for ( Type queryReturn : queryReturnTypes ) {
      if ( queryReturn instanceof EntityType ) {
        if ( metadata != null ) {
          return null;
        }
        EntityType rootReturn = (EntityType) queryReturn;
        OgmEntityPersister persister = (OgmEntityPersister) sessionFactory.getEntityPersister( rootReturn.getName() );
        metadata = new EntityKeyMetadata( persister.getTableName(), persister.getRootTableIdentifierColumnNames() );
      }
    }

    return metadata;
  }

  private QueryParsingResult getQuery(QueryParameters queryParameters) {
    CacheKey cacheKey = new CacheKey( queryParameters.getNamedParameters() );
    QueryParsingResult parsingResult = queryCache.get( cacheKey );

    if ( parsingResult == null ) {
      parsingResult = queryParser.parseQuery(
          sessionFactory,
          query,
          getNamedParameterValuesConvertedByGridType( queryParameters )
      );

      QueryParsingResult cached = queryCache.putIfAbsent( cacheKey, parsingResult );
      if ( cached != null ) {
        parsingResult = cached;
      }
    }

    return parsingResult;
  }

  /**
   * Returns a map with the named parameter values from the given parameters object, converted by the {@link GridType}
   * corresponding to each parameter type.
   */
  private Map<String, Object> getNamedParameterValuesConvertedByGridType(QueryParameters queryParameters) {
    Map<String, Object> parameterValues = new HashMap<String, Object>( queryParameters.getNamedParameters().size() );
    for ( Entry<String, TypedValue> parameter : queryParameters.getNamedParameters().entrySet() ) {
      parameterValues.put( parameter.getKey(), parameter.getValue().getValue() );
    }

    return parameterValues;
  }

  @Override
  public Iterator<?> iterate(QueryParameters queryParameters, EventSource session) throws HibernateException {
    throw new UnsupportedOperationException( "Not yet implemented" );
  }

  @Override
  public ScrollableResults scroll(QueryParameters queryParameters, SessionImplementor session) throws HibernateException {
    throw new UnsupportedOperationException( "Not yet implemented" );
  }

  @Override
  public int executeUpdate(QueryParameters queryParameters, SessionImplementor session) throws HibernateException {
    throw new UnsupportedOperationException( "Not yet implemented" );
  }

  private SelectClause getSelectClause(Map<?, ?> replacements, String collectionRole) throws Exception {
    if ( replacements == null ) {
      replacements = Collections.emptyMap();
    }

    // PHASE 1 : Parse the HQL into an AST.
    final HqlParser parser = parse( true );

    // PHASE 2 : Analyze the HQL AST, and produce an SQL AST.
    final HqlSqlWalker w = analyze( parser, replacements, collectionRole );

    return w.getSelectClause();
  }

  private HqlSqlWalker analyze(HqlParser parser, Map<?, ?> tokenReplacements, String collectionRole) throws QueryException, RecognitionException {
    final HqlSqlWalker w = new HqlSqlWalker( delegate, sessionFactory, parser, tokenReplacements, collectionRole ) {
      @Override
      public Map getEnabledFilters() {
        return filters;
      }
    };
    final AST hqlAst = parser.getAST();

    // Transform the tree.
    w.statement( hqlAst );

    w.getParseErrorHandler().throwQueryException();

    return w;
  }

  private HqlParser parse(boolean filter) throws TokenStreamException, RecognitionException {
    // Parse the query string into an HQL AST.
    final HqlParser parser = HqlParser.getInstance( query );
    parser.setFilter( filter );

    parser.statement();

    final AST hqlAst = parser.getAST();

    final NodeTraverser walker = new NodeTraverser( new JavaConstantConverter() );
    walker.traverseDepthFirst( hqlAst );

    parser.getParseErrorHandler().throwQueryException();
    return parser;
  }

  private static class CacheKey {

    private final Map<String, TypedValue> parameters;
    private final int hashCode;

    public CacheKey(Map<String, TypedValue> parameters) {
      this.parameters = Collections.unmodifiableMap( parameters );
      this.hashCode = parameters.hashCode();
    }

    @Override
    public int hashCode() {
      return hashCode;
    }

    @Override
    public boolean equals(Object obj) {
      if ( this == obj ) {
        return true;
      }
      if ( obj == null ) {
        return false;
      }
      if ( getClass() != obj.getClass() ) {
        return false;
      }
      CacheKey other = (CacheKey) obj;
      if ( parameters == null ) {
        if ( other.parameters != null ) {
          return false;
        }
      }
      else if ( !parameters.equals( other.parameters ) ) {
        return false;
      }
      return true;
    }
  }
}
TOP

Related Classes of org.hibernate.ogm.query.impl.OgmQueryTranslator$CacheKey

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.