/* * 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.Session; 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.QueryParser; import org.hibernate.hql.lucene.LuceneProcessingChain; import org.hibernate.hql.lucene.LuceneQueryParsingResult; import org.hibernate.hql.spi.QueryTranslator; import org.hibernate.internal.util.collections.BoundedConcurrentHashMap; import org.hibernate.ogm.service.impl.SessionFactoryEntityNamesResolver; import org.hibernate.search.FullTextQuery; import org.hibernate.search.FullTextSession; import org.hibernate.search.Search; import org.hibernate.search.engine.ProjectionConstants; import org.hibernate.search.spi.SearchIntegrator; /** * A {@link QueryTranslator} which translates JP-QL queries into equivalent Lucene queries and executes those via * Hibernate Search. * * @author Gunnar Morling */ public class FullTextSearchQueryTranslator extends LegacyParserBridgeQueryTranslator { private final SessionFactoryEntityNamesResolver entityNamesResolver; /** * Lucene does not 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, LuceneQueryParsingResult> luceneQueryCache; public FullTextSearchQueryTranslator(SessionFactoryImplementor sessionFactory, String queryIdentifier, String query, Map<?, ?> filters) { super( sessionFactory, queryIdentifier, query, filters ); entityNamesResolver = new SessionFactoryEntityNamesResolver( sessionFactory ); luceneQueryCache = new BoundedConcurrentHashMap<CacheKey, LuceneQueryParsingResult>( 100, 20, BoundedConcurrentHashMap.Eviction.LIRS ); } @Override public void doCompile(Map replacements, boolean shallow) throws QueryException, MappingException { } @Override public List<?> list(SessionImplementor session, QueryParameters queryParameters) throws HibernateException { FullTextSession fullTextSession = Search.getFullTextSession( (Session) session ); LuceneQueryParsingResult parsingResult = getLuceneQuery( queryParameters, fullTextSession ); FullTextQuery fullTextQuery = fullTextSession.createFullTextQuery( parsingResult.getQuery(), parsingResult.getTargetEntity() ); if ( requiresProjections( parsingResult.getProjections() ) ) { fullTextQuery.setProjection( parsingResult.getProjections().toArray( new String[parsingResult.getProjections().size()] ) ); } fullTextQuery.setSort( parsingResult.getSort() ); if ( queryParameters.getRowSelection().getFirstRow() != null ) { fullTextQuery.setFirstResult( queryParameters.getRowSelection().getFirstRow() ); } if ( queryParameters.getRowSelection().getMaxRows() != null ) { fullTextQuery.setMaxResults( queryParameters.getRowSelection().getMaxRows() ); } return fullTextQuery.list(); } private LuceneQueryParsingResult getLuceneQuery(QueryParameters queryParameters, FullTextSession fullTextSession) { CacheKey cacheKey = new CacheKey( queryParameters.getNamedParameters() ); LuceneQueryParsingResult parsingResult = luceneQueryCache.get( cacheKey ); if ( parsingResult == null ) { parsingResult = new QueryParser().parseQuery( getQueryString(), createProcessingChain( getNamedParameterValues( queryParameters ), fullTextSession ) ); LuceneQueryParsingResult cached = luceneQueryCache.putIfAbsent( cacheKey, parsingResult ); if ( cached != null ) { parsingResult = cached; } } return parsingResult; } @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 boolean requiresProjections(List<String> projections) { if ( projections.size() == 0 ) { return false; } else if ( projections.size() == 1 && ProjectionConstants.THIS.equals( projections.get( 0 ) ) ) { return false; } else { return true; } } private LuceneProcessingChain createProcessingChain(Map<String, Object> namedParameters, FullTextSession fullTextSession) { SearchIntegrator searchFactory = fullTextSession.getSearchFactory().unwrap( SearchIntegrator.class ); return new LuceneProcessingChain.Builder( searchFactory, entityNamesResolver ) .namedParameters( namedParameters ) .buildProcessingChainForClassBasedEntities(); } private Map<String, Object> getNamedParameterValues(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; } 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; } } }