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

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Tue Jul 8 06:28:25 EDT 2008


Author: hardy.ferentschik
Date: 2008-07-08 06:28:25 -0400 (Tue, 08 Jul 2008)
New Revision: 14889

Added:
   search/trunk/src/java/org/hibernate/search/annotations/CachingWrapperFilter.java
   search/trunk/src/java/org/hibernate/search/filter/CachingWrapperFilter.java
Modified:
   search/trunk/src/java/org/hibernate/search/engine/FilterDef.java
   search/trunk/src/java/org/hibernate/search/impl/SearchFactoryImpl.java
   search/trunk/src/java/org/hibernate/search/query/FullTextQueryImpl.java
   search/trunk/src/test/org/hibernate/search/test/filter/ExcludeAllFilterFactory.java
   search/trunk/src/test/org/hibernate/search/test/filter/FilterTest.java
Log:
HSEARCH-174
- Implemented suggested solution. Hibernate Search's CachingWrapperFilter uses now soft references in order to cache filter bit sets.

Added: search/trunk/src/java/org/hibernate/search/annotations/CachingWrapperFilter.java
===================================================================
--- search/trunk/src/java/org/hibernate/search/annotations/CachingWrapperFilter.java	                        (rev 0)
+++ search/trunk/src/java/org/hibernate/search/annotations/CachingWrapperFilter.java	2008-07-08 10:28:25 UTC (rev 14889)
@@ -0,0 +1,26 @@
+// $Id:$
+package org.hibernate.search.annotations;
+
+/**
+ * Defines the strategy for using <code>CachingWrappingFilter</code>
+ * 
+ * @author Hardy Ferentschik
+ * @see org.hibernate.search.filter.CachingWrapperFilter
+ */
+public enum CachingWrapperFilter {
+	/**
+	 * Use a <code>CachingWrapperFilter<code> depending on the value of
+	 * @see FullTextFilterDef#cache()
+	 */
+	AUTOMATIC,
+	
+	/**
+	 * Wrap the filter around a <code>CachingWrappingFilter</code>.
+	 */
+	YES,
+	
+	/**
+	 * Do not use a <code>CachingWrappingFilter</code>.
+	 */
+	NO;
+}
\ No newline at end of file


Property changes on: search/trunk/src/java/org/hibernate/search/annotations/CachingWrapperFilter.java
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: search/trunk/src/java/org/hibernate/search/engine/FilterDef.java
===================================================================
--- search/trunk/src/java/org/hibernate/search/engine/FilterDef.java	2008-07-08 10:26:50 UTC (rev 14888)
+++ search/trunk/src/java/org/hibernate/search/engine/FilterDef.java	2008-07-08 10:28:25 UTC (rev 14889)
@@ -7,18 +7,32 @@
 import java.util.Map;
 
 import org.hibernate.search.SearchException;
+import org.hibernate.search.annotations.CachingWrapperFilter;
 
 /**
+ * A wrapper class which encapsualtes all required information to create a defined filter.
+ * 
  * @author Emmanuel Bernard
  */
 //TODO serialization
+ at SuppressWarnings("unchecked")
 public class FilterDef {
 	private Class impl;
 	private Method factoryMethod;
 	private Method keyMethod;
 	private Map<String, Method> setters = new HashMap<String, Method>();
 	private boolean cache;
+	private CachingWrapperFilter useCachingWrapperFilter;
 
+	public CachingWrapperFilter getUseCachingWrapperFilter() {
+		return useCachingWrapperFilter;
+	}
+
+	public void setUseCachingWrapperFilter(
+			CachingWrapperFilter useCachingWrapperFilter) {
+		this.useCachingWrapperFilter = useCachingWrapperFilter;
+	}
+
 	public Class getImpl() {
 		return impl;
 	}

Added: search/trunk/src/java/org/hibernate/search/filter/CachingWrapperFilter.java
===================================================================
--- search/trunk/src/java/org/hibernate/search/filter/CachingWrapperFilter.java	                        (rev 0)
+++ search/trunk/src/java/org/hibernate/search/filter/CachingWrapperFilter.java	2008-07-08 10:28:25 UTC (rev 14889)
@@ -0,0 +1,73 @@
+package org.hibernate.search.filter;
+
+import java.io.IOException;
+import java.util.BitSet;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.Filter;
+import org.hibernate.util.SoftLimitMRUCache;
+
+/**
+ * A slightly different version of Lucene's original <code>CachingWrapperFilter</code> which
+ * uses <code>SoftReferences</code> instead of <code>WeakReferences</code> in order to cache 
+ * the filter <code>BitSet</code>.
+ * 
+ * @author Hardy Ferentschik
+ * @see org.apache.lucene.search.CachingWrapperFilter
+ * @see <a href="http://opensource.atlassian.com/projects/hibernate/browse/HSEARCH-174">HSEARCH-174</a>
+ */
+ at SuppressWarnings("serial")
+public class CachingWrapperFilter extends Filter {
+	
+	private static final int DEFAULT_SIZE = 128;
+	
+	/**
+	 * The cache using soft references in order to store the filter bit sets.
+	 */
+	protected transient SoftLimitMRUCache cache;
+	
+	protected Filter filter;
+
+	/**
+	 * @param filter
+	 *            Filter to cache results of
+	 */
+	public CachingWrapperFilter(Filter filter) {
+		this.filter = filter;
+	}
+
+	public BitSet bits(IndexReader reader) throws IOException {
+		if (cache == null) {
+			cache = new SoftLimitMRUCache(DEFAULT_SIZE);
+		}
+
+		synchronized (cache) { // check cache
+			BitSet cached = (BitSet) cache.get(reader);
+			if (cached != null) {
+				return cached;
+			}
+		}
+
+		final BitSet bits = filter.bits(reader);
+
+		synchronized (cache) { // update cache
+			cache.put(reader, bits);
+		}
+
+		return bits;
+	}
+
+	public String toString() {
+		return this.getClass().getName() + "(" + filter + ")";
+	}
+
+	public boolean equals(Object o) {
+		if (!(o instanceof CachingWrapperFilter))
+			return false;
+		return this.filter.equals(((CachingWrapperFilter) o).filter);
+	}
+
+	public int hashCode() {
+		return filter.hashCode() ^ 0x1117BF25;
+	}
+}


Property changes on: search/trunk/src/java/org/hibernate/search/filter/CachingWrapperFilter.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Modified: search/trunk/src/java/org/hibernate/search/impl/SearchFactoryImpl.java
===================================================================
--- search/trunk/src/java/org/hibernate/search/impl/SearchFactoryImpl.java	2008-07-08 10:26:50 UTC (rev 14888)
+++ search/trunk/src/java/org/hibernate/search/impl/SearchFactoryImpl.java	2008-07-08 10:28:25 UTC (rev 14889)
@@ -173,6 +173,7 @@
 		FilterDef filterDef = new FilterDef();
 		filterDef.setImpl( defAnn.impl() );
 		filterDef.setCache( defAnn.cache() );
+		filterDef.setUseCachingWrapperFilter( defAnn.useCachingWrapperFilter() );
 		try {
 			filterDef.getImpl().newInstance();
 		}

Modified: search/trunk/src/java/org/hibernate/search/query/FullTextQueryImpl.java
===================================================================
--- search/trunk/src/java/org/hibernate/search/query/FullTextQueryImpl.java	2008-07-08 10:26:50 UTC (rev 14888)
+++ search/trunk/src/java/org/hibernate/search/query/FullTextQueryImpl.java	2008-07-08 10:28:25 UTC (rev 14889)
@@ -1,6 +1,8 @@
 //$Id$
 package org.hibernate.search.query;
 
+import static org.hibernate.search.reader.ReaderProviderHelper.getIndexReaders;
+
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
@@ -38,19 +40,19 @@
 import org.hibernate.search.FullTextFilter;
 import org.hibernate.search.FullTextQuery;
 import org.hibernate.search.SearchException;
+import org.hibernate.search.annotations.CachingWrapperFilter;
 import org.hibernate.search.engine.DocumentBuilder;
 import org.hibernate.search.engine.DocumentExtractor;
 import org.hibernate.search.engine.EntityInfo;
 import org.hibernate.search.engine.FilterDef;
 import org.hibernate.search.engine.Loader;
+import org.hibernate.search.engine.MultiClassesQueryLoader;
 import org.hibernate.search.engine.ProjectionLoader;
 import org.hibernate.search.engine.QueryLoader;
 import org.hibernate.search.engine.SearchFactoryImplementor;
-import org.hibernate.search.engine.MultiClassesQueryLoader;
 import org.hibernate.search.filter.ChainedFilter;
 import org.hibernate.search.filter.FilterKey;
 import org.hibernate.search.reader.ReaderProvider;
-import static org.hibernate.search.reader.ReaderProviderHelper.getIndexReaders;
 import org.hibernate.search.store.DirectoryProvider;
 import org.hibernate.search.util.ContextHelper;
 import org.hibernate.transform.ResultTransformer;
@@ -289,108 +291,167 @@
 	}
 
 	private void buildFilters() {
+		if ( filterDefinitions == null || filterDefinitions.size() == 0 ) {
+			return; // there is nothing to do if we don't have any filter definitions
+		}
+		
+		ChainedFilter chainedFilter = new ChainedFilter();
+		for (FullTextFilterImpl fullTextFilter : filterDefinitions.values()) {
+			Filter filter = buildLuceneFilter(fullTextFilter);
+			chainedFilter.addFilter( filter );
+		}
+		
+		if ( filter != null ) chainedFilter.addFilter( filter );
+		filter = chainedFilter;
+	}
+
+	/**
+	 * Builds a Lucene filter using the given <code>FullTextFilter</code>.
+	 * 
+	 * @param fullTextFilter the Hibernate specific <code>FullTextFilter</code> used to create the 
+	 * Lucene <code>Filter</code>.
+	 * @return the Lucene filter mapped to the filter definition
+	 */
+	private Filter buildLuceneFilter(FullTextFilterImpl fullTextFilter) {
+		
 		SearchFactoryImplementor searchFactoryImplementor = getSearchFactoryImplementor();
-		if ( filterDefinitions != null && filterDefinitions.size() > 0 ) {
-			/*
-			 * FilterKey implementations and Filter(Factory) do not have to be threadsafe wrt their parameter injection
-			 * as FilterCachingStrategy ensure a memory barrier between concurrent thread calls
-			 */
-			ChainedFilter chainedFilter = new ChainedFilter();
-			for (FullTextFilterImpl filterDefinition : filterDefinitions.values()) {
-				FilterDef def = searchFactoryImplementor.getFilterDefinition( filterDefinition.getName() );
-				Class implClass = def.getImpl();
-				Object instance;
-				try {
-					instance = implClass.newInstance();
-				}
-				catch (InstantiationException e) {
-					throw new SearchException( "Unable to create @FullTextFilterDef: " + def.getImpl(), e );
-				}
-				catch (IllegalAccessException e) {
-					throw new SearchException( "Unable to create @FullTextFilterDef: " + def.getImpl(), e );
-				}
-				for (Map.Entry<String, Object> entry : filterDefinition.getParameters().entrySet()) {
-					def.invoke( entry.getKey(), instance, entry.getValue() );
-				}
-				if ( def.isCache() && def.getKeyMethod() == null && filterDefinition.getParameters().size() > 0 ) {
-					throw new SearchException( "Filter with parameters and no @Key method: " + filterDefinition.getName() );
-				}
+		
+		/*
+		 * FilterKey implementations and Filter(Factory) do not have to be threadsafe wrt their parameter injection
+		 * as FilterCachingStrategy ensure a memory barrier between concurrent thread calls
+		 */
+		FilterDef def = searchFactoryImplementor.getFilterDefinition( fullTextFilter.getName() );
+		Object instance = createFilterInstance(fullTextFilter, def);
+		FilterKey key = createFilterKey(def, instance);
 
-				FilterKey key = null;
-				if ( def.isCache() ) {
-					if ( def.getKeyMethod() == null ) {
-						key = new FilterKey() {
-							public int hashCode() {
-								return getImpl().hashCode();
-							}
+		// try to get the filter out of the cache
+		Filter filter = def.isCache() ?
+				searchFactoryImplementor.getFilterCachingStrategy().getCachedFilter( key ) :
+				null;
+					
+		if ( filter == null ) {
+			filter = createFilter(def, instance);	
+			
+			// add filter to cache if we have to
+			if ( def.isCache() ) {
+				searchFactoryImplementor.getFilterCachingStrategy().addCachedFilter( key, filter );
+			}
+		}
+		return filter;
+	}
 
-							public boolean equals(Object obj) {
-								if ( !( obj instanceof FilterKey ) ) return false;
-								FilterKey that = (FilterKey) obj;
-								return this.getImpl().equals( that.getImpl() );
-							}
-						};
-					}
-					else {
-						try {
-							key = (FilterKey) def.getKeyMethod().invoke( instance );
-						}
-						catch (IllegalAccessException e) {
-							throw new SearchException( "Unable to access @Key method: "
-									+ def.getImpl().getName() + "." + def.getKeyMethod().getName() );
-						}
-						catch (InvocationTargetException e) {
-							throw new SearchException( "Unable to access @Key method: "
-									+ def.getImpl().getName() + "." + def.getKeyMethod().getName() );
-						}
-						catch (ClassCastException e) {
-							throw new SearchException( "@Key method does not return FilterKey: "
-									+ def.getImpl().getName() + "." + def.getKeyMethod().getName() );
-						}
-					}
-					key.setImpl( def.getImpl() );
+	private Filter createFilter(FilterDef def, Object instance) {
+		Filter filter = null;
+		if ( def.getFactoryMethod() != null ) {
+			try {
+				filter = (Filter) def.getFactoryMethod().invoke( instance );
+			}
+			catch (IllegalAccessException e) {
+				throw new SearchException( "Unable to access @Factory method: "
+						+ def.getImpl().getName() + "." + def.getFactoryMethod().getName() );
+			}
+			catch (InvocationTargetException e) {
+				throw new SearchException( "Unable to access @Factory method: "
+						+ def.getImpl().getName() + "." + def.getFactoryMethod().getName() );
+			}
+			catch (ClassCastException e) {
+				throw new SearchException( "@Key method does not return a org.apache.lucene.search.Filter class: "
+						+ def.getImpl().getName() + "." + def.getFactoryMethod().getName() );
+			}
+		}
+		else {
+			try {
+				filter = (Filter) instance;
+			}
+			catch (ClassCastException e) {
+				throw new SearchException( "@Key method does not return a org.apache.lucene.search.Filter class: "
+						+ def.getImpl().getName() + "." + def.getFactoryMethod().getName() );
+			}
+		}
+		
+		filter = addCachingWrapperFilter(filter, def);
+		return filter;
+	}
+
+	/**
+	 * Decides whether to wrap the given filter around a <code>CachingWrapperFilter<code>.
+	 * 
+	 * @param filter the filter which maybe gets wrapped.
+	 * @param def The filter definition used to decide whether wrapping should occur or not.
+	 * @return The original filter or wrapped filter depending on the information extracted from 
+	 * <code>def</code>.
+	 */
+	private Filter addCachingWrapperFilter(Filter filter, FilterDef def) {
+		if (def.getUseCachingWrapperFilter() == CachingWrapperFilter.YES
+				|| (def.getUseCachingWrapperFilter() == CachingWrapperFilter.AUTOMATIC && def
+						.isCache())) {
+			filter = new org.hibernate.search.filter.CachingWrapperFilter(filter);
+		}
+		
+		return filter;
+	}
+
+	private FilterKey createFilterKey(FilterDef def, Object instance) {
+		FilterKey key = null;
+		if ( !def.isCache() ) {
+			return key; // if the filter is not cached there is no key!
+		}
+				
+		if ( def.getKeyMethod() == null ) {
+			key = new FilterKey() {
+				public int hashCode() {
+					return getImpl().hashCode();
 				}
 
-				Filter filter = def.isCache() ?
-						searchFactoryImplementor.getFilterCachingStrategy().getCachedFilter( key ) :
-						null;
-				if ( filter == null ) {
-					if ( def.getFactoryMethod() != null ) {
-						try {
-							filter = (Filter) def.getFactoryMethod().invoke( instance );
-						}
-						catch (IllegalAccessException e) {
-							throw new SearchException( "Unable to access @Factory method: "
-									+ def.getImpl().getName() + "." + def.getFactoryMethod().getName() );
-						}
-						catch (InvocationTargetException e) {
-							throw new SearchException( "Unable to access @Factory method: "
-									+ def.getImpl().getName() + "." + def.getFactoryMethod().getName() );
-						}
-						catch (ClassCastException e) {
-							throw new SearchException( "@Key method does not return a org.apache.lucene.search.Filter class: "
-									+ def.getImpl().getName() + "." + def.getFactoryMethod().getName() );
-						}
-					}
-					else {
-						try {
-							filter = (Filter) instance;
-						}
-						catch (ClassCastException e) {
-							throw new SearchException( "@Key method does not return a org.apache.lucene.search.Filter class: "
-									+ def.getImpl().getName() + "." + def.getFactoryMethod().getName() );
-						}
-					}
-					if ( def.isCache() )
-						searchFactoryImplementor.getFilterCachingStrategy().addCachedFilter( key, filter );
+				public boolean equals(Object obj) {
+					if ( !( obj instanceof FilterKey ) ) return false;
+					FilterKey that = (FilterKey) obj;
+					return this.getImpl().equals( that.getImpl() );
 				}
-				chainedFilter.addFilter( filter );
+			};
+		}
+		else {
+			try {
+				key = (FilterKey) def.getKeyMethod().invoke( instance );
 			}
-			if ( filter != null ) chainedFilter.addFilter( filter );
-			filter = chainedFilter;
+			catch (IllegalAccessException e) {
+				throw new SearchException( "Unable to access @Key method: "
+						+ def.getImpl().getName() + "." + def.getKeyMethod().getName() );
+			}
+			catch (InvocationTargetException e) {
+				throw new SearchException( "Unable to access @Key method: "
+						+ def.getImpl().getName() + "." + def.getKeyMethod().getName() );
+			}
+			catch (ClassCastException e) {
+				throw new SearchException( "@Key method does not return FilterKey: "
+						+ def.getImpl().getName() + "." + def.getKeyMethod().getName() );
+			}
 		}
+		key.setImpl( def.getImpl() );
+		return key;
 	}
 
+	private Object createFilterInstance(FullTextFilterImpl fullTextFilter,
+			FilterDef def) {
+		Object instance;
+		try {
+			instance = def.getImpl().newInstance();
+		}
+		catch (InstantiationException e) {
+			throw new SearchException( "Unable to create @FullTextFilterDef: " + def.getImpl(), e );
+		}
+		catch (IllegalAccessException e) {
+			throw new SearchException( "Unable to create @FullTextFilterDef: " + def.getImpl(), e );
+		}
+		for (Map.Entry<String, Object> entry : fullTextFilter.getParameters().entrySet()) {
+			def.invoke( entry.getKey(), instance, entry.getValue() );
+		}
+		if ( def.isCache() && def.getKeyMethod() == null && fullTextFilter.getParameters().size() > 0 ) {
+			throw new SearchException( "Filter with parameters and no @Key method: " + fullTextFilter.getName() );
+		}
+		return instance;
+	}
+
 	private org.apache.lucene.search.Query filterQueryByClasses(org.apache.lucene.search.Query luceneQuery) {
 		if ( ! needClassFilterClause ) {
 			return luceneQuery;

Modified: search/trunk/src/test/org/hibernate/search/test/filter/ExcludeAllFilterFactory.java
===================================================================
--- search/trunk/src/test/org/hibernate/search/test/filter/ExcludeAllFilterFactory.java	2008-07-08 10:26:50 UTC (rev 14888)
+++ search/trunk/src/test/org/hibernate/search/test/filter/ExcludeAllFilterFactory.java	2008-07-08 10:28:25 UTC (rev 14889)
@@ -1,9 +1,9 @@
-//$
+// $Id:$
 package org.hibernate.search.test.filter;
 
-import org.apache.lucene.search.CachingWrapperFilter;
 import org.apache.lucene.search.Filter;
 import org.hibernate.search.annotations.Factory;
+import org.hibernate.search.filter.CachingWrapperFilter;
 
 /**
  * @author Emmanuel Bernard


Property changes on: search/trunk/src/test/org/hibernate/search/test/filter/ExcludeAllFilterFactory.java
___________________________________________________________________
Name: svn:keywords
   + Id

Modified: search/trunk/src/test/org/hibernate/search/test/filter/FilterTest.java
===================================================================
--- search/trunk/src/test/org/hibernate/search/test/filter/FilterTest.java	2008-07-08 10:26:50 UTC (rev 14888)
+++ search/trunk/src/test/org/hibernate/search/test/filter/FilterTest.java	2008-07-08 10:28:25 UTC (rev 14889)
@@ -1,21 +1,19 @@
-//$Id$
+// $Id:$
 package org.hibernate.search.test.filter;
 
-import java.util.Date;
 import java.util.Calendar;
 
-import org.hibernate.search.test.SearchTestCase;
-import org.hibernate.search.FullTextSession;
-import org.hibernate.search.Search;
-import org.hibernate.search.FullTextQuery;
-import org.hibernate.Session;
-import org.apache.lucene.search.Query;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.TermQuery;
-import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.Filter;
 import org.apache.lucene.search.RangeFilter;
-import org.apache.lucene.index.Term;
+import org.apache.lucene.search.TermQuery;
+import org.hibernate.Session;
+import org.hibernate.search.FullTextQuery;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.search.Search;
+import org.hibernate.search.test.SearchTestCase;
 
 /**
  * @author Emmanuel Bernard
@@ -69,6 +67,11 @@
 		ftQuery = s.createFullTextQuery( query, Driver.class );
 		ftQuery.enableFullTextFilter( "cachetest");
 		assertEquals("Should filter out all", 0, ftQuery.getResultSize() );
+		
+		// HSEARCH-174 - we call System.gc() to force a garbage collection. Prior to the fix
+		// to HSEARCH-174 this would cause the filter to be garbage collected since Lucene 
+		// used weak references in the cache.
+		System.gc();
 
 		ftQuery = s.createFullTextQuery( query, Driver.class );
 		ftQuery.enableFullTextFilter( "cachetest");
@@ -158,6 +161,7 @@
 		s.close();
 	}
 
+	@SuppressWarnings("unchecked")
 	protected Class[] getMappings() {
 		return new Class[] {
 				Driver.class,


Property changes on: search/trunk/src/test/org/hibernate/search/test/filter/FilterTest.java
___________________________________________________________________
Name: svn:keywords
   + Id




More information about the hibernate-commits mailing list