[hibernate-commits] Hibernate SVN: r14940 - in search/trunk/src: java/org/hibernate/search/engine and 5 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Wed Jul 16 20:15:47 EDT 2008


Author: epbernard
Date: 2008-07-16 20:15:47 -0400 (Wed, 16 Jul 2008)
New Revision: 14940

Added:
   search/trunk/src/test/org/hibernate/search/test/query/explain/
   search/trunk/src/test/org/hibernate/search/test/query/explain/Dvd.java
   search/trunk/src/test/org/hibernate/search/test/query/explain/ExplanationTest.java
Modified:
   search/trunk/src/java/org/hibernate/search/FullTextQuery.java
   search/trunk/src/java/org/hibernate/search/ProjectionConstants.java
   search/trunk/src/java/org/hibernate/search/engine/DocumentExtractor.java
   search/trunk/src/java/org/hibernate/search/jpa/FullTextQuery.java
   search/trunk/src/java/org/hibernate/search/jpa/impl/FullTextQueryImpl.java
   search/trunk/src/java/org/hibernate/search/query/FullTextQueryImpl.java
Log:
HSEARCH-154 add explain method

Modified: search/trunk/src/java/org/hibernate/search/FullTextQuery.java
===================================================================
--- search/trunk/src/java/org/hibernate/search/FullTextQuery.java	2008-07-16 18:08:17 UTC (rev 14939)
+++ search/trunk/src/java/org/hibernate/search/FullTextQuery.java	2008-07-17 00:15:47 UTC (rev 14940)
@@ -3,6 +3,7 @@
 
 import org.apache.lucene.search.Filter;
 import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.Explanation;
 import org.hibernate.Criteria;
 import org.hibernate.Query;
 import org.hibernate.transform.ResultTransformer;
@@ -78,6 +79,16 @@
 	void disableFullTextFilter(String name);
 
 	/**
+	 * Return the Lucene {@link org.apache.lucene.search.Explanation}
+	 * object describing the score computation for the matching object/document
+	 * in the current query
+	 *
+	 * @param documentId Lucene Document id to be explain. This is NOT the object id
+	 * @return Lucene Explanation
+	 */
+	Explanation explain(int documentId);
+
+	/**
 	 * {link:Query#setFirstResult}
 	 */
 	FullTextQuery setFirstResult(int firstResult);

Modified: search/trunk/src/java/org/hibernate/search/ProjectionConstants.java
===================================================================
--- search/trunk/src/java/org/hibernate/search/ProjectionConstants.java	2008-07-16 18:08:17 UTC (rev 14939)
+++ search/trunk/src/java/org/hibernate/search/ProjectionConstants.java	2008-07-17 00:15:47 UTC (rev 14940)
@@ -36,6 +36,16 @@
 	 */
 	public String DOCUMENT_ID = "__HSearch_DocumentId";
 	/**
+	 * Lucene {@link org.apache.lucene.search.Explanation} object describing the score computation for
+	 * the matching object/document
+	 * This feature is relatively expensive, do not use unless you return a limited
+	 * amount of objects (using pagination)
+	 * To retrieve explanation of a single result, consider retrieving {@link #DOCUMENT_ID}
+	 * and using fullTextQuery.explain(int)
+	 */
+	public String EXPLANATION = "__HSearch_Explanation";
+	
+	/**
 	 * Object class
 	 */
 	//TODO OBJECT CLASS

Modified: search/trunk/src/java/org/hibernate/search/engine/DocumentExtractor.java
===================================================================
--- search/trunk/src/java/org/hibernate/search/engine/DocumentExtractor.java	2008-07-16 18:08:17 UTC (rev 14939)
+++ search/trunk/src/java/org/hibernate/search/engine/DocumentExtractor.java	2008-07-17 00:15:47 UTC (rev 14940)
@@ -6,6 +6,8 @@
 
 import org.apache.lucene.document.Document;
 import org.apache.lucene.search.Hits;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
 import org.hibernate.search.engine.EntityInfo;
 import org.hibernate.search.ProjectionConstants;
 
@@ -16,10 +18,14 @@
 public class DocumentExtractor {
 	private final SearchFactoryImplementor searchFactoryImplementor;
 	private final String[] projection;
+	private final IndexSearcher searcher;
+	private final Query preparedQuery;
 
-	public DocumentExtractor(SearchFactoryImplementor searchFactoryImplementor, String... projection) {
+	public DocumentExtractor(Query preparedQuery, IndexSearcher searcher, SearchFactoryImplementor searchFactoryImplementor, String... projection) {
 		this.searchFactoryImplementor = searchFactoryImplementor;
 		this.projection = projection;
+		this.searcher = searcher;
+		this.preparedQuery = preparedQuery;
 	}
 
 	private EntityInfo extract(Document document) {
@@ -56,6 +62,9 @@
 				else if ( ProjectionConstants.BOOST.equals( projection[x] ) ) {
 					eip[x] = doc.getBoost();
 				}
+				else if ( ProjectionConstants.EXPLANATION.equals( projection[x] ) ) {
+					eip[x] = searcher.explain( preparedQuery, hits.id( index ) );
+				}
 				else if ( ProjectionConstants.THIS.equals( projection[x] ) ) {
 					//THIS could be projected more than once
 					//THIS loading delayed to the Loader phase

Modified: search/trunk/src/java/org/hibernate/search/jpa/FullTextQuery.java
===================================================================
--- search/trunk/src/java/org/hibernate/search/jpa/FullTextQuery.java	2008-07-16 18:08:17 UTC (rev 14939)
+++ search/trunk/src/java/org/hibernate/search/jpa/FullTextQuery.java	2008-07-17 00:15:47 UTC (rev 14940)
@@ -5,6 +5,7 @@
 
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.search.Filter;
+import org.apache.lucene.search.Explanation;
 import org.hibernate.Criteria;
 import org.hibernate.transform.ResultTransformer;
 import org.hibernate.search.ProjectionConstants;
@@ -89,4 +90,14 @@
 	 *
 	 */
 	FullTextQuery setResultTransformer(ResultTransformer transformer);
+
+	/**
+	 * Return the Lucene {@link org.apache.lucene.search.Explanation}
+	 * object describing the score computation for the matching object/document
+	 * in the current query
+	 *
+	 * @param documentId Lucene Document id to be explain. This is NOT the object id
+	 * @return Lucene Explanation
+	 */
+	Explanation explain(int documentId);
 }

Modified: search/trunk/src/java/org/hibernate/search/jpa/impl/FullTextQueryImpl.java
===================================================================
--- search/trunk/src/java/org/hibernate/search/jpa/impl/FullTextQueryImpl.java	2008-07-16 18:08:17 UTC (rev 14939)
+++ search/trunk/src/java/org/hibernate/search/jpa/impl/FullTextQueryImpl.java	2008-07-17 00:15:47 UTC (rev 14940)
@@ -19,6 +19,7 @@
 
 import org.apache.lucene.search.Filter;
 import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.Explanation;
 import org.hibernate.Criteria;
 import org.hibernate.FlushMode;
 import org.hibernate.HibernateException;
@@ -227,6 +228,10 @@
 		return this;
 	}
 
+	public Explanation explain(int documentId) {
+		return query.explain( documentId );
+	}
+
 	public int executeUpdate() {
 		throw new IllegalStateException( "Update not allowed in FullTextQueries" );
 	}

Modified: search/trunk/src/java/org/hibernate/search/query/FullTextQueryImpl.java
===================================================================
--- search/trunk/src/java/org/hibernate/search/query/FullTextQueryImpl.java	2008-07-16 18:08:17 UTC (rev 14939)
+++ search/trunk/src/java/org/hibernate/search/query/FullTextQueryImpl.java	2008-07-17 00:15:47 UTC (rev 14940)
@@ -25,6 +25,7 @@
 import org.apache.lucene.search.Similarity;
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.Explanation;
 import org.hibernate.Criteria;
 import org.hibernate.HibernateException;
 import org.hibernate.LockMode;
@@ -129,17 +130,17 @@
 			return new IteratorImpl( Collections.EMPTY_LIST, noLoader );
 		}
 		try {
-			Hits hits = getHits( searcher );
+			QueryAndHits queryAndHits = getQueryAndHits( searcher );
 			int first = first();
-			int max = max( first, hits );
+			int max = max( first, queryAndHits.hits );
 			Session sess = (Session) this.session;
 
 			int size = max - first + 1 < 0 ? 0 : max - first + 1;
 			List<EntityInfo> infos = new ArrayList<EntityInfo>( size );
-			DocumentExtractor extractor = new DocumentExtractor( searchFactoryImplementor, indexProjection );
+			DocumentExtractor extractor = new DocumentExtractor( queryAndHits.preparedQuery, searcher, searchFactoryImplementor, indexProjection );
 			for (int index = first; index <= max; index++) {
 				//TODO use indexSearcher.getIndexReader().document( hits.id(index), FieldSelector(indexProjection) );
-				infos.add( extractor.extract( hits, index ) );
+				infos.add( extractor.extract( queryAndHits.hits, index ) );
 			}
 			Loader loader = getLoader( sess, searchFactoryImplementor );
 			return new IteratorImpl( infos, loader );
@@ -207,14 +208,13 @@
 		//find the directories
 		IndexSearcher searcher = buildSearcher( searchFactory );
 		//FIXME: handle null searcher
-		Hits hits;
 		try {
-			hits = getHits( searcher );
+			QueryAndHits queryAndHits = getQueryAndHits( searcher );
 			int first = first();
-			int max = max( first, hits );
-			DocumentExtractor extractor = new DocumentExtractor( searchFactory, indexProjection );
+			int max = max( first, queryAndHits.hits );
+			DocumentExtractor extractor = new DocumentExtractor( queryAndHits.preparedQuery, searcher, searchFactory, indexProjection );
 			Loader loader = getLoader( (Session) this.session, searchFactory );
-			return new ScrollableResultsImpl( searcher, hits, first, max, fetchSize, extractor, loader, searchFactory );
+			return new ScrollableResultsImpl( searcher, queryAndHits.hits, first, max, fetchSize, extractor, loader, searchFactory );
 		}
 		catch (IOException e) {
 			//close only in case of exception
@@ -238,18 +238,17 @@
 		//find the directories
 		IndexSearcher searcher = buildSearcher( searchFactoryImplementor );
 		if ( searcher == null ) return Collections.EMPTY_LIST;
-		Hits hits;
 		try {
-			hits = getHits( searcher );
+			QueryAndHits queryAndHits = getQueryAndHits( searcher );
 			int first = first();
-			int max = max( first, hits );
+			int max = max( first, queryAndHits.hits );
 			Session sess = (Session) this.session;
 
 			int size = max - first + 1 < 0 ? 0 : max - first + 1;
 			List<EntityInfo> infos = new ArrayList<EntityInfo>( size );
-			DocumentExtractor extractor = new DocumentExtractor( searchFactoryImplementor, indexProjection );
+			DocumentExtractor extractor = new DocumentExtractor( queryAndHits.preparedQuery, searcher, searchFactoryImplementor, indexProjection );
 			for (int index = first; index <= max; index++) {
-				infos.add( extractor.extract( hits, index ) );
+				infos.add( extractor.extract( queryAndHits.hits, index ) );
 			}
 			Loader loader = getLoader( sess, searchFactoryImplementor );
 			List list = loader.load( infos.toArray( new EntityInfo[infos.size()] ) );
@@ -274,6 +273,34 @@
 		}
 	}
 
+	public Explanation explain(int documentId) {
+		Explanation explanation = null;
+		SearchFactoryImplementor searchFactoryImplementor = getSearchFactoryImplementor();
+		Searcher searcher = buildSearcher( searchFactoryImplementor );
+		if (searcher == null) {
+			throw new SearchException("Unable to build explanation for document id:"
+					+ documentId + ". no index found");
+		}
+		try {
+			org.apache.lucene.search.Query query = filterQueryByClasses( luceneQuery );
+			buildFilters();
+			explanation = searcher.explain( query, documentId );
+		}
+		catch (IOException e) {
+			throw new HibernateException( "Unable to query Lucene index and build explanation", e );
+		}
+		finally {
+			//searcher cannot be null
+			try {
+				closeSearcher( searcher, searchFactoryImplementor.getReaderProvider() );
+			}
+			catch (SearchException e) {
+				log.warn( "Unable to properly close searcher during lucene query: " + getQueryString(), e );
+			}
+		}
+		return explanation;
+	}
+
 	/**
 	 * Execute the lucene search and return the machting hits.
 	 *
@@ -281,13 +308,13 @@
 	 * @return The lucene hits.
 	 * @throws IOException in case there is an error executing the lucene search.
 	 */
-	private Hits getHits(Searcher searcher) throws IOException {
+	private QueryAndHits getQueryAndHits(Searcher searcher) throws IOException {
 		Hits hits;
 		org.apache.lucene.search.Query query = filterQueryByClasses( luceneQuery );
 		buildFilters();
 		hits = searcher.search( query, filter, sort );
 		setResultSize( hits );
-		return hits;
+		return new QueryAndHits( query, hits );
 	}
 
 	private void buildFilters() {
@@ -600,7 +627,7 @@
 			else {
 				Hits hits;
 				try {
-					hits = getHits( searcher );
+					hits = getQueryAndHits( searcher ).hits;
 					resultSize = hits.length();
 				}
 				catch (IOException e) {
@@ -720,4 +747,14 @@
 			throw new UnsupportedOperationException( "noLoader should not be used" );
 		}
 	};
+
+	private static class QueryAndHits {
+		private QueryAndHits(org.apache.lucene.search.Query preparedQuery, Hits hits) {
+			this.preparedQuery = preparedQuery;
+			this.hits = hits;
+		}
+
+		public final org.apache.lucene.search.Query preparedQuery;
+		public final Hits hits;
+	}
 }

Added: search/trunk/src/test/org/hibernate/search/test/query/explain/Dvd.java
===================================================================
--- search/trunk/src/test/org/hibernate/search/test/query/explain/Dvd.java	                        (rev 0)
+++ search/trunk/src/test/org/hibernate/search/test/query/explain/Dvd.java	2008-07-17 00:15:47 UTC (rev 14940)
@@ -0,0 +1,52 @@
+package org.hibernate.search.test.query.explain;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.GeneratedValue;
+
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.DocumentId;
+import org.hibernate.search.annotations.Field;
+
+/**
+ * @author Emmanuel Bernard
+ */
+ at Entity
+ at Indexed
+public class Dvd {
+	@Id @GeneratedValue @DocumentId private Integer id;
+	private @Field String title;
+	private @Field String description;
+
+	protected Dvd() {
+	}
+
+	public Dvd(String title, String description) {
+		this.title = title;
+		this.description = description;
+	}
+
+	public Integer getId() {
+		return id;
+	}
+
+	public void setId(Integer id) {
+		this.id = id;
+	}
+
+	public String getTitle() {
+		return title;
+	}
+
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public void setDescription(String description) {
+		this.description = description;
+	}
+}

Added: search/trunk/src/test/org/hibernate/search/test/query/explain/ExplanationTest.java
===================================================================
--- search/trunk/src/test/org/hibernate/search/test/query/explain/ExplanationTest.java	                        (rev 0)
+++ search/trunk/src/test/org/hibernate/search/test/query/explain/ExplanationTest.java	2008-07-17 00:15:47 UTC (rev 14940)
@@ -0,0 +1,55 @@
+package org.hibernate.search.test.query.explain;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+
+import org.hibernate.search.test.SearchTestCase;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.search.Search;
+import org.hibernate.search.FullTextQuery;
+import org.hibernate.Transaction;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Explanation;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.queryParser.MultiFieldQueryParser;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class ExplanationTest extends SearchTestCase {
+	public void testExplanation() throws Exception {
+		FullTextSession s = Search.getFullTextSession( openSession() );
+		Transaction tx = s.beginTransaction();
+		Dvd dvd = new Dvd("The dark knight", "Batman returns with it best enomy the Jocker. The dark side of this movies shows up pretty quickly");
+		s.persist( dvd );
+		dvd = new Dvd("Wall-e", "The tiny little robot comes to Eartch after the dark times and tries to clean it");
+		s.persist( dvd );
+		tx.commit();
+		s.clear();
+
+		tx = s.beginTransaction();
+		Map<String, Float> boosts = new HashMap<String, Float>(2);
+		boosts.put( "title", new Float(4) );
+		boosts.put( "description", new Float(1) );
+		MultiFieldQueryParser parser = new MultiFieldQueryParser(new String[] {"title", "description"}, new StandardAnalyzer(), boosts);
+		Query luceneQuery = parser.parse( "dark" );
+		FullTextQuery ftQuery = s.createFullTextQuery( luceneQuery, Dvd.class )
+				.setProjection( FullTextQuery.DOCUMENT_ID, FullTextQuery.EXPLANATION, FullTextQuery.THIS );
+		@SuppressWarnings("unchecked") List<Object[]> results = ftQuery.list();
+		assertEquals( 2, results.size() );
+		for (Object[] result : results) {
+			assertEquals( ftQuery.explain( (Integer) result[0] ).toString(), result[1].toString() );
+			s.delete( result[2] );
+		}
+		tx.commit();
+		s.close();
+
+	}
+	protected Class[] getMappings() {
+		return new Class[] {
+				Dvd.class
+		};
+	}
+}




More information about the hibernate-commits mailing list