[hibernate-commits] Hibernate SVN: r12813 - in trunk/HibernateExt/search/src: java/org/hibernate/search/annotations and 6 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Tue Jul 24 22:58:26 EDT 2007


Author: epbernard
Date: 2007-07-24 22:58:26 -0400 (Tue, 24 Jul 2007)
New Revision: 12813

Added:
   trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextFilter.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/annotations/Factory.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/annotations/FullTextFilterDef.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/annotations/FullTextFilterDefs.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/annotations/Key.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/engine/FilterDef.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/filter/
   trunk/HibernateExt/search/src/java/org/hibernate/search/filter/ChainedFilter.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/filter/FilterCachingStrategy.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/filter/FilterKey.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/filter/MRUFilterCachingStrategy.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/filter/StandardFilterKey.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextFilterImpl.java
   trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/
   trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/BestDriversFilter.java
   trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/Driver.java
   trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/FilterTest.java
   trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/SecurityFilterFactory.java
Modified:
   trunk/HibernateExt/search/src/java/org/hibernate/search/Environment.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/engine/SearchFactoryImplementor.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/impl/SearchFactoryImpl.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java
Log:
HSEARCH-15 provide ftQuery.setFilter(Filter) (Hardy Ferentschik)
HSEARCH-58 Support named Filters (and caching)

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/Environment.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/Environment.java	2007-07-24 20:35:47 UTC (rev 12812)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/Environment.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -56,4 +56,12 @@
 	 * define the reader strategy used
 	 */
 	public static final String READER_STRATEGY = READER_PREFIX + "strategy";
+	/**
+	 * filter caching strategy
+	 */
+	public static final String FILTER_CACHING_STRATEGY_PREFIX = "hibernate.search.filter.cache_strategy.";
+	/**
+	 * filter caching strategy class (must have a no-arg constructor and implements FilterCachingStrateg
+	 */
+	public static final String FILTER_CACHING_STRATEGY_IMPL = "hibernate.search.filter.cache_strategy.impl";
 }

Added: trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextFilter.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextFilter.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextFilter.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -0,0 +1,13 @@
+//$Id$
+package org.hibernate.search;
+
+/**
+ * represents a FullTextFilter that is about to be applied
+ * Used to inject parameters
+ *
+ * @author Emmanuel Bernard
+ */
+public interface FullTextFilter {
+	FullTextFilter setParameter(String name, Object value);
+	Object getParameter(String name);
+}

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java	2007-07-24 20:35:47 UTC (rev 12812)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -1,9 +1,10 @@
 //$Id$
 package org.hibernate.search;
 
+import org.apache.lucene.search.Filter;
 import org.apache.lucene.search.Sort;
+import org.hibernate.Criteria;
 import org.hibernate.Query;
-import org.hibernate.Criteria;
 
 /**
  * The base interface for lucene powered searches.
@@ -24,6 +25,15 @@
 	 * @return this for method chaining
 	 */
 	FullTextQuery setSort(Sort sort);
+	
+	/**
+	 * Allows to use lucene filters.
+	 * Semi-deprecated? a preferred way is to use the @FullTextFilterDef approach
+	 *
+	 * @param filter The lucene filter.
+	 * @return this for method chaining
+	 */
+	FullTextQuery setFilter(Filter filter);
 
 	/**
 	 * Returns the number of hits for this search
@@ -57,4 +67,15 @@
 	 *
 	 */
 	FullTextQuery setIndexProjection(String... fields);
+
+	/**
+	 * Enable a given filter by its name. Returns a FullTextFilter object that allows filter parameter injection
+	 */
+	FullTextFilter enableFullTextFilter(String name);
+
+	/**
+	 * Disable a given filter by its name
+	 */
+	void disableFullTextFilter(String name);
+
 }

Added: trunk/HibernateExt/search/src/java/org/hibernate/search/annotations/Factory.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/annotations/Factory.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/annotations/Factory.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -0,0 +1,24 @@
+//$Id$
+package org.hibernate.search.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Documented;
+
+/**
+ * Marks a method as a factory method for a given type.
+ * A factory method is called whenever a new instance of a given
+ * type is requested.
+ * The factory method is used with a higher priority than a plain no-arg constructor when present
+ *
+ * @Factory currently works for @FullTextFilterDef.impl classes
+ *
+ * @author Emmanuel Bernard
+ */
+ at Retention( RetentionPolicy.RUNTIME )
+ at Target( ElementType.METHOD )
+ at Documented
+public @interface Factory {
+}

Added: trunk/HibernateExt/search/src/java/org/hibernate/search/annotations/FullTextFilterDef.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/annotations/FullTextFilterDef.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/annotations/FullTextFilterDef.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -0,0 +1,35 @@
+//$Id$
+package org.hibernate.search.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Documented;
+
+/**
+ * Defines a FullTextFilter that can be optionally applied to
+ * every FullText Queries
+ * While not related to a specific indexed entity, the annotation has to be set on one of them
+ *
+ * @author Emmanuel Bernard
+ */
+ at Retention( RetentionPolicy.RUNTIME )
+ at Target( { ElementType.TYPE } )
+ at Documented
+public @interface FullTextFilterDef {
+	/**
+	 * Filter name. Must be unique accross all mappings for a given persistence unit
+	 */
+	String name();
+
+	/**
+	 * Either implements org.apache.lucene.search.Filter
+	 * or contains a @Factory method returning one.
+	 * The Filter generated must be thread-safe
+	 *
+	 * If the filter accept parameters, an @Key method must be present as well
+	 *
+	 */
+	Class impl();
+}

Added: trunk/HibernateExt/search/src/java/org/hibernate/search/annotations/FullTextFilterDefs.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/annotations/FullTextFilterDefs.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/annotations/FullTextFilterDefs.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -0,0 +1,20 @@
+//$Id$
+package org.hibernate.search.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Documented;
+
+/**
+ * A list of FullTextFilterDef
+ *
+ * @author Emmanuel Bernard
+ */
+ at Retention( RetentionPolicy.RUNTIME )
+ at Target( { ElementType.TYPE } )
+ at Documented
+public @interface FullTextFilterDefs {
+	FullTextFilterDef[] value();
+}

Added: trunk/HibernateExt/search/src/java/org/hibernate/search/annotations/Key.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/annotations/Key.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/annotations/Key.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -0,0 +1,25 @@
+//$Id$
+package org.hibernate.search.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Documented;
+
+/**
+ * Marks a method as a key constructor for a given type.
+ * A key is an object that uniquely identify a given object type and a given set of parameters
+ *
+ * The key object must implement equals / hashcode so that 2 keys are equals iif
+ * the given target object types are the same, the set of parameters are the same.
+ *
+ * @Factory currently works for @FullTextFilterDef.impl classes
+ *
+ * @author Emmanuel Bernard
+ */
+ at Retention( RetentionPolicy.RUNTIME )
+ at Target( ElementType.METHOD )
+ at Documented
+public @interface Key {
+}

Added: trunk/HibernateExt/search/src/java/org/hibernate/search/engine/FilterDef.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/engine/FilterDef.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/engine/FilterDef.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -0,0 +1,63 @@
+//$Id$
+package org.hibernate.search.engine;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.hibernate.search.SearchException;
+
+/**
+ * @author Emmanuel Bernard
+ */
+//TODO serialization
+public class FilterDef {
+	private Class impl;
+	private Method factoryMethod;
+	private Method keyMethod;
+	private Map<String, Method> setters = new HashMap<String, Method>();
+
+	public Class getImpl() {
+		return impl;
+	}
+
+	public void setImpl(Class impl) {
+		this.impl = impl;
+	}
+
+	public Method getFactoryMethod() {
+		return factoryMethod;
+	}
+
+	public void setFactoryMethod(Method factoryMethod) {
+		this.factoryMethod = factoryMethod;
+	}
+
+	public Method getKeyMethod() {
+		return keyMethod;
+	}
+
+	public void setKeyMethod(Method keyMethod) {
+		this.keyMethod = keyMethod;
+	}
+
+	public void addSetter(String name, Method method) {
+		if ( method.isAccessible() ) method.setAccessible( true );
+		setters.put( name, method );
+	}
+
+	public void invoke(String parameterName, Object filter, Object parameterValue) {
+		Method method = setters.get( parameterName );
+		if ( method == null ) throw new SearchException( "No setter " + parameterName + " found in " + this.impl );
+		try {
+			method.invoke( filter, parameterValue );
+		}
+		catch (IllegalAccessException e) {
+			throw new SearchException( "Unable to set Filter parameter: " + parameterName + " on filter class: " + this.impl, e );
+		}
+		catch (InvocationTargetException e) {
+			throw new SearchException( "Unable to set Filter parameter: " + parameterName + " on filter class: " + this.impl, e );
+		}
+	}
+}

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/engine/SearchFactoryImplementor.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/engine/SearchFactoryImplementor.java	2007-07-24 20:35:47 UTC (rev 12812)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/engine/SearchFactoryImplementor.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -5,6 +5,7 @@
 import java.util.concurrent.locks.ReentrantLock;
 
 import org.hibernate.search.SearchFactory;
+import org.hibernate.search.filter.FilterCachingStrategy;
 import org.hibernate.search.store.DirectoryProvider;
 import org.hibernate.search.store.optimization.OptimizerStrategy;
 import org.hibernate.search.backend.BackendQueueProcessorFactory;
@@ -30,7 +31,11 @@
 
 	void addOptimizerStrategy(DirectoryProvider<?> provider, OptimizerStrategy optimizerStrategy);
 
-	public OptimizerStrategy getOptimizerStrategy(DirectoryProvider<?> provider);
+	OptimizerStrategy getOptimizerStrategy(DirectoryProvider<?> provider);
+
+	FilterCachingStrategy getFilterCachingStrategy();
+
+	FilterDef getFilterDefinition(String name);
 	
 	public LuceneIndexingParameters getIndexingParameters(DirectoryProvider<?> provider );
 	

Added: trunk/HibernateExt/search/src/java/org/hibernate/search/filter/ChainedFilter.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/filter/ChainedFilter.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/filter/ChainedFilter.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -0,0 +1,43 @@
+//$Id$
+package org.hibernate.search.filter;
+
+import java.util.BitSet;
+import java.util.List;
+import java.util.ArrayList;
+import java.io.IOException;
+
+import org.apache.lucene.search.Filter;
+import org.apache.lucene.index.IndexReader;
+import org.hibernate.annotations.common.AssertionFailure;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class ChainedFilter extends Filter {
+	private List<Filter> chainedFilters = new ArrayList<Filter>();
+
+
+	public void addFilter(Filter filter) {
+		this.chainedFilters.add( filter );
+	}
+
+	public BitSet bits(IndexReader reader) throws IOException {
+		if (chainedFilters.size() == 0) throw new AssertionFailure("Chainedfilter has no filters to chain for");
+		//we need to copy the first BitSet because BitSet is modified by .logicalOp
+		Filter filter = chainedFilters.get( 0 );
+		BitSet result = (BitSet) filter.bits( reader ).clone();
+		for (int index = 1 ; index < chainedFilters.size() ; index++) {
+			result.and( chainedFilters.get( index ).bits( reader ) );
+		}
+		return result;
+	}
+
+
+	public String toString() {
+		StringBuilder sb = new StringBuilder("ChainedFilter [");
+		for (Filter filter : chainedFilters) {
+			sb.append( "\n  ").append( filter.toString() );
+		}
+		return sb.append("\n]" ).toString();
+	}
+}

Added: trunk/HibernateExt/search/src/java/org/hibernate/search/filter/FilterCachingStrategy.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/filter/FilterCachingStrategy.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/filter/FilterCachingStrategy.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -0,0 +1,28 @@
+//$Id$
+package org.hibernate.search.filter;
+
+import java.util.Properties;
+
+import org.apache.lucene.search.Filter;
+
+/**
+ * Defines the caching filter strategy
+ *
+ * @author Emmanuel Bernard
+ */
+public interface FilterCachingStrategy {
+	/**
+	 * initialize the strategy from the properties
+	 * The Properties must not be changed
+	 */
+	void initialize(Properties properties);
+	/**
+	 * Retrieve the cached filter for a given key or null if not cached
+	 */
+	Filter getCachedFilter(FilterKey key);
+
+	/**
+	 * Propose a candidate filter for caching
+	 */
+	void addCachedFilter(FilterKey key, Filter filter);
+}

Added: trunk/HibernateExt/search/src/java/org/hibernate/search/filter/FilterKey.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/filter/FilterKey.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/filter/FilterKey.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -0,0 +1,31 @@
+//$Id$
+package org.hibernate.search.filter;
+
+/**
+ * The key object must implement equals / hashcode so that 2 keys are equals if and only if
+ * the given Filter types are the same and the set of parameters are the same.
+ *
+ * The FilterKey creator (ie the @Key method) does not have to inject <code>impl</code>
+ * It will be done by Hibernate Search
+ *
+ * @author Emmanuel Bernard
+ */
+public abstract class FilterKey {
+
+	private Class impl;
+
+	/**
+	 * Represent the @FullTextFilterDef.impl class
+	 */
+	public Class getImpl() {
+		return impl;
+	}
+
+	public void setImpl(Class impl) {
+		this.impl = impl;
+	}
+
+	public abstract int hashCode();
+
+	public abstract boolean equals(Object obj);
+}

Added: trunk/HibernateExt/search/src/java/org/hibernate/search/filter/MRUFilterCachingStrategy.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/filter/MRUFilterCachingStrategy.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/filter/MRUFilterCachingStrategy.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -0,0 +1,46 @@
+//$Id$
+package org.hibernate.search.filter;
+
+import java.util.Properties;
+
+import org.apache.lucene.search.Filter;
+import org.hibernate.search.Environment;
+import org.hibernate.search.SearchException;
+import org.hibernate.util.SoftLimitMRUCache;
+
+/**
+ * Keep the most recently used Filters in the cache
+ * The cache is at least as big as <code>hibernate.search.filter.cache_strategy.size</code>
+ * Above this limit, Filters are kept as soft references
+ *
+ * @author Emmanuel Bernard
+ */
+public class MRUFilterCachingStrategy implements FilterCachingStrategy {
+	private static final String DEFAULT_SIZE = "128";
+	private SoftLimitMRUCache cache;
+
+	public void initialize(Properties properties) {
+		int size;
+		try {
+			size = Integer.parseInt(
+					properties.getProperty( Environment.FILTER_CACHING_STRATEGY_PREFIX + "size", DEFAULT_SIZE )
+			);
+		}
+		catch (NumberFormatException nfe) {
+			throw new SearchException(
+					"Unable to parse " + Environment.FILTER_CACHING_STRATEGY_PREFIX + "size: "
+							+ properties.getProperty( Environment.FILTER_CACHING_STRATEGY_PREFIX + "size", DEFAULT_SIZE ), nfe
+			);
+		}
+
+		cache = new SoftLimitMRUCache( size );
+	}
+
+	public Filter getCachedFilter(FilterKey key) {
+		return (Filter) cache.get( key );
+	}
+
+	public void addCachedFilter(FilterKey key, Filter filter) {
+		cache.put( key, filter );
+	}
+}

Added: trunk/HibernateExt/search/src/java/org/hibernate/search/filter/StandardFilterKey.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/filter/StandardFilterKey.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/filter/StandardFilterKey.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -0,0 +1,59 @@
+//$Id$
+package org.hibernate.search.filter;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Implements a filter key usign all injected parameters to compute
+ * equals and hashCode
+ * the order the parameters are added is significant
+ *
+ * @author Emmanuel Bernard
+ */
+public class StandardFilterKey extends FilterKey {
+	private List parameters = new ArrayList();
+	private boolean implSet;
+
+
+	public void setImpl(Class impl) {
+		super.setImpl( impl );
+		//add impl once and only once
+		if (implSet) {
+			parameters.set( 0, impl );
+		}
+		else {
+			implSet = true;
+			parameters.add( 0, impl );
+		}
+	}
+
+	public void addParameter(Object value) {
+		parameters.add( value );
+	}
+	public int hashCode() {
+		int hash = 23;
+		for (Object param : parameters) {
+			hash = 31*hash + (param != null ? param.hashCode() : 0);
+		}
+		return hash;
+	}
+
+	public boolean equals(Object obj) {
+		if ( ! ( obj instanceof StandardFilterKey ) ) return false;
+		StandardFilterKey that = (StandardFilterKey) obj;
+		int size = parameters.size();
+		if ( size != that.parameters.size() ) return false;
+		for (int index = 0 ; index < size; index++) {
+			Object paramThis = parameters.get( index );
+			Object paramThat = that.parameters.get( index );
+			if (paramThis == null && paramThat != null) return false;
+			if (paramThis != null && ! paramThis.equals( paramThat ) ) return false;
+		}
+		return true;
+	}
+}

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/impl/SearchFactoryImpl.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/impl/SearchFactoryImpl.java	2007-07-24 20:35:47 UTC (rev 12812)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/impl/SearchFactoryImpl.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -1,26 +1,36 @@
 //$Id$
 package org.hibernate.search.impl;
 
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.WeakHashMap;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Properties;
 import java.util.concurrent.locks.ReentrantLock;
+import java.lang.reflect.Method;
+import java.beans.Introspector;
 
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
 import org.hibernate.annotations.common.reflection.ReflectionManager;
 import org.hibernate.annotations.common.reflection.XClass;
 import org.hibernate.annotations.common.reflection.java.JavaReflectionManager;
+import org.hibernate.annotations.common.util.StringHelper;
 import org.hibernate.cfg.Configuration;
 import org.hibernate.mapping.PersistentClass;
 import org.hibernate.search.Environment;
 import org.hibernate.search.SearchException;
 import org.hibernate.search.Version;
+import org.hibernate.search.filter.FilterCachingStrategy;
+import org.hibernate.search.filter.MRUFilterCachingStrategy;
 import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.FullTextFilterDef;
+import org.hibernate.search.annotations.Factory;
+import org.hibernate.search.annotations.Key;
+import org.hibernate.search.annotations.FullTextFilterDefs;
 import org.hibernate.search.backend.BackendQueueProcessorFactory;
 import org.hibernate.search.backend.LuceneIndexingParameters;
 import org.hibernate.search.backend.LuceneWork;
@@ -29,6 +39,7 @@
 import org.hibernate.search.backend.WorkerFactory;
 import org.hibernate.search.engine.DocumentBuilder;
 import org.hibernate.search.engine.SearchFactoryImplementor;
+import org.hibernate.search.engine.FilterDef;
 import org.hibernate.search.reader.ReaderProvider;
 import org.hibernate.search.reader.ReaderProviderFactory;
 import org.hibernate.search.store.DirectoryProvider;
@@ -37,7 +48,6 @@
 import org.hibernate.util.ReflectHelper;
 
 /**
- * 
  * @author Emmanuel Bernard
  */
 public class SearchFactoryImpl implements SearchFactoryImplementor {
@@ -47,7 +57,7 @@
 	static {
 		Version.touch();
 	}
-	
+
 	private Map<Class, DocumentBuilder<Object>> documentBuilders = new HashMap<Class, DocumentBuilder<Object>>();
 	//keep track of the index modifiers per DirectoryProvider since multiple entity can use the same directory provider
 	private Map<DirectoryProvider, ReentrantLock> lockableDirectoryProviders =
@@ -57,8 +67,9 @@
 	private Worker worker;
 	private ReaderProvider readerProvider;
 	private BackendQueueProcessorFactory backendQueueProcessorFactory;
-	
-	
+	private Map<String, FilterDef> filterDefinitions;
+	private FilterCachingStrategy filterCachingStrategy;
+
 	/**
 	 * Each directory provider (index) can have its own performance settings.
 	 */
@@ -81,15 +92,72 @@
 
 		Analyzer analyzer = initAnalyzer(cfg);
 		initDocumentBuilders(cfg, reflectionManager, analyzer);
-		
+
 		Set<Class> indexedClasses = documentBuilders.keySet();
-		for (DocumentBuilder builder : documentBuilders.values()) {	
+		for (DocumentBuilder builder : documentBuilders.values()) {
 			builder.postInitialize( indexedClasses );
 		}
 		worker = WorkerFactory.createWorker( cfg, this );
 		readerProvider = ReaderProviderFactory.createReaderProvider( cfg, this );
+		buildFilterCachingStrategy( cfg.getProperties() );
 	}
 
+	private void bindFilterDefs(XClass mappedXClass) {
+		filterDefinitions = new HashMap<String, FilterDef>();
+		FullTextFilterDef defAnn = mappedXClass.getAnnotation( FullTextFilterDef.class );
+		if ( defAnn != null ) {
+			bindFilterDef( defAnn, mappedXClass );
+		}
+		FullTextFilterDefs defsAnn = mappedXClass.getAnnotation( FullTextFilterDefs.class );
+		if (defsAnn != null) {
+			for ( FullTextFilterDef def : defsAnn.value() ) {
+				bindFilterDef( def, mappedXClass );
+			}
+		}
+	}
+
+	private void bindFilterDef(FullTextFilterDef defAnn, XClass mappedXClass) {
+		if ( filterDefinitions.containsKey( defAnn.name() ) ) {
+			throw new SearchException("Multiple definition of @FullTextFilterDef.name=" + defAnn.name() + ": "
+					+ mappedXClass.getName() );
+		}
+		FilterDef filterDef = new FilterDef();
+		filterDef.setImpl( defAnn.impl() );
+		try {
+			filterDef.getImpl().newInstance();
+		}
+		catch (IllegalAccessException e) {
+			throw new SearchException("Unable to create Filter class: " + filterDef.getImpl().getName(), e);
+		}
+		catch (InstantiationException e) {
+			throw new SearchException("Unable to create Filter class: " + filterDef.getImpl().getName(), e);
+		}
+		for ( Method method : filterDef.getImpl().getMethods() ) {
+			if ( method.isAnnotationPresent( Factory.class ) ) {
+				if ( filterDef.getFactoryMethod() != null ) {
+					throw new SearchException("Multiple @Factory methods found" + defAnn.name() + ": "
+							+ filterDef.getImpl().getName() + "." + method.getName() );
+				}
+				if ( !method.isAccessible() ) method.setAccessible( true );
+				filterDef.setFactoryMethod( method );
+			}
+			if ( method.isAnnotationPresent( Key.class ) ) {
+				if ( filterDef.getKeyMethod() != null ) {
+					throw new SearchException("Multiple @Key methods found" + defAnn.name() + ": "
+							+ filterDef.getImpl().getName() + "." + method.getName() );
+				}
+				if ( !method.isAccessible() ) method.setAccessible( true );
+				filterDef.setKeyMethod( method );
+			}
+
+			String name = method.getName();
+			if ( name.startsWith( "set" ) && method.getParameterTypes().length == 1 ) {
+				filterDef.addSetter( Introspector.decapitalize( name.substring( 3 ) ), method );
+			}
+		}
+		filterDefinitions.put( defAnn.name(), filterDef );
+	}
+
 	//code doesn't have to be multithreaded because SF creation is not.
 	//this is not a public API, should really only be used during the SessionFActory building
 	//FIXME this is ugly, impl.staticmethod, fix that
@@ -124,7 +192,7 @@
 	public void addOptimizerStrategy(DirectoryProvider<?> provider, OptimizerStrategy optimizerStrategy) {
 		dirProviderOptimizerStrategies.put( provider, optimizerStrategy );
 	}
-	
+
 	public void addIndexingParmeters(DirectoryProvider<?> provider, LuceneIndexingParameters indexingParams) {
 		dirProviderIndexingParams.put( provider, indexingParams );
 	}
@@ -132,10 +200,10 @@
 	public OptimizerStrategy getOptimizerStrategy(DirectoryProvider<?> provider) {
 		return dirProviderOptimizerStrategies.get( provider );
 	}
-	
+
 	public LuceneIndexingParameters getIndexingParameters(DirectoryProvider<?> provider ) {
 		return dirProviderIndexingParams.get( provider );
-	}	
+	}
 
 	public ReaderProvider getReaderProvider() {
 		return readerProvider;
@@ -178,7 +246,7 @@
 		queue.add( new OptimizeLuceneWork( entityType ) );
 		getBackendQueueProcessorFactory().getProcessor( queue ).run();
 	}
-			
+
 	private void initDocumentBuilders(Configuration cfg, ReflectionManager reflectionManager, Analyzer analyzer) {
 		Iterator iter = cfg.getClassMappings();
 		DirectoryProviderFactory factory = new DirectoryProviderFactory();
@@ -187,16 +255,20 @@
 			Class<?> mappedClass = clazz.getMappedClass();
 			if (mappedClass != null) {
 				XClass mappedXClass = reflectionManager.toXClass(mappedClass);
-				if (mappedXClass != null && mappedXClass.isAnnotationPresent(Indexed.class)) {
-					DirectoryProvider provider = factory.createDirectoryProvider(mappedXClass, cfg, this);
-					// TODO move that into DirectoryProviderFactory
-					if (!lockableDirectoryProviders.containsKey(provider)) {
-						lockableDirectoryProviders.put(provider, new ReentrantLock());
+				if ( mappedXClass != null) {
+					if ( mappedXClass.isAnnotationPresent( Indexed.class ) ) {
+						DirectoryProvider provider = factory.createDirectoryProvider( mappedXClass, cfg, this );
+						//TODO move that into DirectoryProviderFactory
+						if ( !lockableDirectoryProviders.containsKey( provider ) ) {
+							lockableDirectoryProviders.put( provider, new ReentrantLock() );
+						}
+						final DocumentBuilder<Object> documentBuilder = new DocumentBuilder<Object>(
+								mappedXClass, analyzer, provider, reflectionManager
+						);
+
+						documentBuilders.put( mappedClass, documentBuilder );
 					}
-					final DocumentBuilder<Object> documentBuilder = new DocumentBuilder<Object>(mappedXClass, analyzer,
-							provider, reflectionManager);
-
-					documentBuilders.put(mappedClass, documentBuilder);
+					bindFilterDefs(mappedXClass);
 				}
 			}
 		}
@@ -204,7 +276,7 @@
 
 	/**
 	 * Initilises the Lucene analyzer to use by reading the analyzer class from the configuration and instantiating it.
-	 * 
+	 *
 	 * @param cfg
 	 *            The current configuration.
 	 * @return The Lucene analyzer to use for tokenisation.
@@ -233,5 +305,36 @@
 			throw new SearchException("Failed to instantiate lucene analyzer with type " + analyzerClassName, e);
 		}
 		return defaultAnalyzer;
-	}	
+	}
+
+	private void buildFilterCachingStrategy(Properties properties) {
+		String impl = properties.getProperty( Environment.FILTER_CACHING_STRATEGY_IMPL );
+		if ( StringHelper.isEmpty( impl ) || "mru".equalsIgnoreCase( impl ) ) {
+			filterCachingStrategy = new MRUFilterCachingStrategy();
+		}
+		else {
+			try {
+				Class filterCachingStrategyClass = org.hibernate.annotations.common.util.ReflectHelper.classForName( impl, SearchFactoryImpl.class );
+				filterCachingStrategy = (FilterCachingStrategy) filterCachingStrategyClass.newInstance();
+			}
+			catch (ClassNotFoundException e) {
+				throw new SearchException( "Unable to find filterCachingStrategy class: " + impl, e );
+			}
+			catch (IllegalAccessException e) {
+				throw new SearchException( "Unable to instanciate filterCachingStrategy class: " + impl, e );
+			}
+			catch (InstantiationException e) {
+				throw new SearchException( "Unable to instanciate filterCachingStrategy class: " + impl, e );
+			}
+		}
+		filterCachingStrategy.initialize( properties );
+	}
+
+	public FilterCachingStrategy getFilterCachingStrategy() {
+		return filterCachingStrategy;
+	}
+
+	public FilterDef getFilterDefinition(String name) {
+		return filterDefinitions.get( name );
+	}
 }

Added: trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextFilterImpl.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextFilterImpl.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextFilterImpl.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -0,0 +1,37 @@
+//$Id$
+package org.hibernate.search.query;
+
+import java.util.Map;
+import java.util.HashMap;
+
+import org.hibernate.search.FullTextFilter;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class FullTextFilterImpl implements FullTextFilter {
+	private Map<String, Object> parameters = new HashMap<String, Object>();
+	private String name;
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public FullTextFilter setParameter(String name, Object value) {
+		parameters.put( name, value );
+		return this;
+	}
+
+	public Object getParameter(String name) {
+		return parameters.get( name );
+	}
+
+
+	public Map<String, Object> getParameters() {
+		return parameters;
+	}
+}

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java	2007-07-24 20:35:47 UTC (rev 12812)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -4,17 +4,20 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.lang.reflect.InvocationTargetException;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Filter;
 import org.apache.lucene.search.Hits;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Searcher;
@@ -32,16 +35,20 @@
 import org.hibernate.engine.query.ParameterMetadata;
 import org.hibernate.impl.AbstractQueryImpl;
 import org.hibernate.impl.CriteriaImpl;
+import org.hibernate.search.FullTextFilter;
 import org.hibernate.search.FullTextQuery;
 import org.hibernate.search.SearchException;
 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.ObjectLoader;
 import org.hibernate.search.engine.ProjectionLoader;
 import org.hibernate.search.engine.QueryLoader;
 import org.hibernate.search.engine.SearchFactoryImplementor;
+import org.hibernate.search.filter.ChainedFilter;
+import org.hibernate.search.filter.FilterKey;
 import org.hibernate.search.store.DirectoryProvider;
 import org.hibernate.search.util.ContextHelper;
 
@@ -61,8 +68,11 @@
 	private Integer maxResults;
 	private Integer resultSize;
 	private Sort sort;
+	private Filter filter;
 	private Criteria criteria;
 	private String[] indexProjection;
+	private SearchFactoryImplementor searchFactoryImplementor;
+	private Map<String, FullTextFilterImpl> filterDefinitions;
 
 	/**
 	 * classes must be immutable
@@ -75,13 +85,23 @@
 		this.classes = classes;
 	}
 
-
+	/**
+	 * {@inheritDoc}
+	 */
 	public FullTextQuery setSort(Sort sort) {
 		this.sort = sort;
 		return this;
 	}
 
 	/**
+	 * {@inheritDoc}
+	 */
+	public FullTextQuery setFilter(Filter filter) {
+		this.filter = filter;
+		return this;
+	}
+
+	/**
 	 * Return an interator on the results.
 	 * Retrieve the object one by one (initialize it during the next() operation)
 	 */
@@ -242,16 +262,104 @@
 	private Hits getHits(Searcher searcher) throws IOException {
 		Hits hits;
 		org.apache.lucene.search.Query query = filterQueryByClasses( luceneQuery );
-		if ( sort == null ) {
-			hits = searcher.search( query );
-		}
-		else {
-			hits = searcher.search( query, sort );
-		}
+		buildFilters();
+		hits = searcher.search( query, filter, sort );
 		setResultSize( hits );
 		return hits;
 	}
 
+	private void buildFilters() {
+		SearchFactoryImplementor searchFactoryImplementor = getSearchFactoryImplementor();
+		if ( filterDefinitions != null && filterDefinitions.size() > 0 ) {
+			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.getKeyMethod() == null && filterDefinition.getParameters().size() > 0 ) {
+					throw new SearchException("Filter with parameters and no @Key method: " + filterDefinition.getName() );
+				}
+				FilterKey key;
+				if ( def.getKeyMethod() == null ) {
+					key = new FilterKey( ) {
+						public int hashCode() {
+							return getImpl().hashCode();
+						}
+
+						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() );
+				Filter filter = searchFactoryImplementor.getFilterCachingStrategy().getCachedFilter( key );
+				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() );
+						}
+					}
+					searchFactoryImplementor.getFilterCachingStrategy().addCachedFilter( key, filter );
+				}
+				chainedFilter.addFilter( filter );
+			}
+			if ( filter != null ) chainedFilter.addFilter( filter );
+			filter = chainedFilter;
+		}
+	}
+
 	private org.apache.lucene.search.Query filterQueryByClasses(org.apache.lucene.search.Query luceneQuery) {
 		//A query filter is more practical than a manual class filtering post query (esp on scrollable resultsets)
 		//it also probably minimise the memory footprint
@@ -404,6 +512,34 @@
 		return null;
 	}
 
+	public FullTextFilter enableFullTextFilter(String name) {
+		if ( filterDefinitions == null ) {
+			filterDefinitions = new HashMap<String, FullTextFilterImpl>();
+		}
+		FullTextFilterImpl filterDefinition = filterDefinitions.get( name );
+		if ( filterDefinition != null ) return filterDefinition;
+
+		filterDefinition = new FullTextFilterImpl();
+		filterDefinition.setName( name );
+		FilterDef filterDef = getSearchFactoryImplementor().getFilterDefinition( name );
+		if (filterDef == null) {
+			throw new SearchException("Unkown @FullTextFilter: " + name);
+		}
+		filterDefinitions.put(name, filterDefinition);
+		return filterDefinition;
+	}
+
+	public void disableFullTextFilter(String name) {
+		filterDefinitions.remove( name );
+	}
+
+	private SearchFactoryImplementor getSearchFactoryImplementor() {
+		if ( searchFactoryImplementor == null ) {
+			searchFactoryImplementor = ContextHelper.getSearchFactoryBySFI( session );
+		}
+		return searchFactoryImplementor;
+	}
+
 	private static Loader noLoader = new Loader() {
 		public void init(Session session, SearchFactoryImplementor searchFactoryImplementor) {
 		}

Added: trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/BestDriversFilter.java
===================================================================
--- trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/BestDriversFilter.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/BestDriversFilter.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -0,0 +1,25 @@
+//$Id$
+package org.hibernate.search.test.filter;
+
+import java.util.BitSet;
+import java.io.IOException;
+
+import org.apache.lucene.search.Filter;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.index.Term;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class BestDriversFilter extends Filter {
+
+	public BitSet bits(IndexReader reader) throws IOException {
+		BitSet bitSet = new BitSet( reader.maxDoc() );
+		TermDocs termDocs = reader.termDocs( new Term("score", "5") );
+		while ( termDocs.next() ) {
+			bitSet.set( termDocs.doc() );
+		}
+		return bitSet;
+	}
+}

Added: trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/Driver.java
===================================================================
--- trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/Driver.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/Driver.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -0,0 +1,104 @@
+//$Id$
+package org.hibernate.search.test.filter;
+
+import java.util.Date;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.DocumentId;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Index;
+import org.hibernate.search.annotations.DateBridge;
+import org.hibernate.search.annotations.Resolution;
+import org.hibernate.search.annotations.FullTextFilterDef;
+import org.hibernate.search.annotations.FullTextFilterDefs;
+
+/**
+ * @author Emmanuel Bernard
+ */
+ at Entity
+ at Indexed
+ at FullTextFilterDefs( {
+		@FullTextFilterDef(name = "bestDriver", impl = BestDriversFilter.class), //actual Filter implementation
+		@FullTextFilterDef(name = "security", impl = SecurityFilterFactory.class) //Filter factory with parameters
+})
+public class Driver {
+	@Id
+	@DocumentId
+	private int id;
+	@Field(index= Index.TOKENIZED)
+	private String name;
+	@Field(index= Index.UN_TOKENIZED)
+	private String teacher;
+	@Field(index= Index.UN_TOKENIZED)
+	private int score;
+	@Field(index= Index.UN_TOKENIZED)
+	@DateBridge( resolution = Resolution.YEAR)
+	private Date delivery;
+
+	public int getId() {
+		return id;
+	}
+
+	public void setId(int id) {
+		this.id = id;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String getTeacher() {
+		return teacher;
+	}
+
+	public void setTeacher(String teacher) {
+		this.teacher = teacher;
+	}
+
+	public int getScore() {
+		return score;
+	}
+
+	public void setScore(int score) {
+		this.score = score;
+	}
+
+	public Date getDelivery() {
+		return delivery;
+	}
+
+	public void setDelivery(Date delivery) {
+		this.delivery = delivery;
+	}
+
+	public boolean equals(Object o) {
+		if ( this == o ) return true;
+		if ( o == null || getClass() != o.getClass() ) return false;
+
+		Driver driver = (Driver) o;
+
+		if ( id != driver.id ) return false;
+		if ( score != driver.score ) return false;
+		if ( delivery != null ? !delivery.equals( driver.delivery ) : driver.delivery != null ) return false;
+		if ( name != null ? !name.equals( driver.name ) : driver.name != null ) return false;
+		if ( teacher != null ? !teacher.equals( driver.teacher ) : driver.teacher != null ) return false;
+
+		return true;
+	}
+
+	public int hashCode() {
+		int result;
+		result = id;
+		result = 31 * result + ( name != null ? name.hashCode() : 0 );
+		result = 31 * result + ( teacher != null ? teacher.hashCode() : 0 );
+		result = 31 * result + score;
+		result = 31 * result + ( delivery != null ? delivery.hashCode() : 0 );
+		return result;
+	}
+}

Added: trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/FilterTest.java
===================================================================
--- trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/FilterTest.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/FilterTest.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -0,0 +1,137 @@
+//$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.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;
+
+/**
+ * @author Emmanuel Bernard
+ * @author Hardy Ferentschik
+ */
+public class FilterTest extends SearchTestCase {
+
+	public void testNamedFilters() {
+		createData();
+		FullTextSession s = Search.createFullTextSession( openSession( ) );
+		s.getTransaction().begin();
+		BooleanQuery query = new BooleanQuery();
+		query.add( new TermQuery( new Term("teacher", "andre") ), BooleanClause.Occur.SHOULD );
+		query.add( new TermQuery( new Term("teacher", "max") ), BooleanClause.Occur.SHOULD );
+		query.add( new TermQuery( new Term("teacher", "aaron") ), BooleanClause.Occur.SHOULD );
+		FullTextQuery ftQuery = s.createFullTextQuery( query, Driver.class );
+		assertEquals("No filter should happen", 3, ftQuery.getResultSize() );
+
+		ftQuery = s.createFullTextQuery( query, Driver.class );
+		ftQuery.enableFullTextFilter( "bestDriver");
+		assertEquals("Should filter out Gavin", 2, ftQuery.getResultSize() );
+
+		ftQuery = s.createFullTextQuery( query, Driver.class );
+		ftQuery.enableFullTextFilter( "bestDriver");
+		ftQuery.enableFullTextFilter( "security").setParameter( "login", "andre" );
+		assertEquals("Should filter to limit to Emmanuel", 1, ftQuery.getResultSize() );
+
+		ftQuery = s.createFullTextQuery( query, Driver.class );
+		ftQuery.enableFullTextFilter( "bestDriver");
+		ftQuery.enableFullTextFilter( "security").setParameter( "login", "andre" );
+		ftQuery.disableFullTextFilter( "security");
+		ftQuery.disableFullTextFilter( "bestDriver");
+		assertEquals("Should not filter anymore", 3, ftQuery.getResultSize() );
+
+		s.getTransaction().commit();
+		s.close();
+		deleteData();
+	}
+
+	public void testStraightFilters() {
+		createData();
+		FullTextSession s = Search.createFullTextSession( openSession( ) );
+		s.getTransaction().begin();
+		BooleanQuery query = new BooleanQuery();
+		query.add( new TermQuery( new Term("teacher", "andre") ), BooleanClause.Occur.SHOULD );
+		query.add( new TermQuery( new Term("teacher", "max") ), BooleanClause.Occur.SHOULD );
+		query.add( new TermQuery( new Term("teacher", "aaron") ), BooleanClause.Occur.SHOULD );
+		FullTextQuery ftQuery;
+
+		ftQuery = s.createFullTextQuery( query, Driver.class );
+		ftQuery.enableFullTextFilter( "bestDriver");
+		Filter dateFilter = new RangeFilter("delivery", "2001", "2005",
+				true, true);
+		ftQuery.setFilter( dateFilter );
+		assertEquals("Should select only liz", 1, ftQuery.getResultSize() );
+
+		ftQuery = s.createFullTextQuery( query, Driver.class );
+		ftQuery.setFilter( dateFilter );
+		ftQuery.enableFullTextFilter( "bestDriver");
+		ftQuery.enableFullTextFilter( "security").setParameter( "login", "andre" );
+		ftQuery.disableFullTextFilter( "security");
+		ftQuery.disableFullTextFilter( "bestDriver");
+		ftQuery.setFilter( null );
+		assertEquals("Should not filter anymore", 3, ftQuery.getResultSize() );
+
+		s.getTransaction().commit();
+		s.close();
+		deleteData();
+	}
+
+
+	private void deleteData() {
+		Session s = openSession( );
+		s.getTransaction().begin();
+		s.createQuery( "delete " + Driver.class.getName() + " t").executeUpdate();
+		s.getTransaction().commit();
+		s.close();
+	}
+
+	private void createData() {
+		Session s = openSession( );
+		s.getTransaction().begin();
+		Calendar cal = Calendar.getInstance();
+		cal.set( 2006, 10, 11);
+		Driver driver = new Driver();
+		driver.setDelivery( cal.getTime() );
+		driver.setId( 1 );
+		driver.setName( "Emmanuel" );
+		driver.setScore( 5 );
+		driver.setTeacher( "andre" );
+		s.persist( driver );
+
+		cal.set( 2007, 10, 11);
+		driver = new Driver();
+		driver.setDelivery( cal.getTime() );
+		driver.setId( 2 );
+		driver.setName( "Gavin" );
+		driver.setScore( 3 );
+		driver.setTeacher( "aaron" );
+		s.persist( driver );
+
+		cal.set( 2004, 10, 11);
+		driver = new Driver();
+		driver.setDelivery( cal.getTime() );
+		driver.setId( 3 );
+		driver.setName( "Liz" );
+		driver.setScore( 5 );
+		driver.setTeacher( "max" );
+		s.persist( driver );
+		s.getTransaction().commit();
+		s.close();
+	}
+
+	protected Class[] getMappings() {
+		return new Class[] {
+				Driver.class
+		};
+	}
+}

Added: trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/SecurityFilterFactory.java
===================================================================
--- trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/SecurityFilterFactory.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/test/org/hibernate/search/test/filter/SecurityFilterFactory.java	2007-07-25 02:58:26 UTC (rev 12813)
@@ -0,0 +1,43 @@
+//$Id$
+package org.hibernate.search.test.filter;
+
+import org.hibernate.search.filter.FilterKey;
+import org.hibernate.search.filter.StandardFilterKey;
+import org.hibernate.search.annotations.Key;
+import org.hibernate.search.annotations.Factory;
+import org.apache.lucene.search.Filter;
+import org.apache.lucene.search.QueryFilter;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.QueryWrapperFilter;
+import org.apache.lucene.search.CachingWrapperFilter;
+import org.apache.lucene.index.Term;
+
+/**
+ * Apply a security filter to the results
+ *
+ * @author Emmanuel Bernard
+ */
+public class SecurityFilterFactory {
+	private String login;
+
+	/**
+	 * injected parameter
+	 */
+	public void setLogin(String login) {
+		this.login = login;
+	}
+
+	@Key
+	public FilterKey getKey() {
+		StandardFilterKey key = new StandardFilterKey();
+		key.addParameter( login );
+		return key;
+	}
+
+	@Factory
+	public Filter getFilter() {
+		Query query = new TermQuery( new Term("teacher", login) );
+		return new CachingWrapperFilter( new QueryWrapperFilter(query) );
+	}
+}




More information about the hibernate-commits mailing list