[hibernate-commits] Hibernate SVN: r14009 - in search/trunk/src: java/org/hibernate/search/bridge and 3 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Wed Sep 12 23:48:51 EDT 2007


Author: epbernard
Date: 2007-09-12 23:48:51 -0400 (Wed, 12 Sep 2007)
New Revision: 14009

Added:
   search/trunk/src/java/org/hibernate/search/annotations/ClassBridge.java
   search/trunk/src/test/org/hibernate/search/test/bridge/CatFieldsClassBridge.java
   search/trunk/src/test/org/hibernate/search/test/bridge/ClassBridgeTest.java
   search/trunk/src/test/org/hibernate/search/test/bridge/Department.java
Modified:
   search/trunk/src/java/org/hibernate/search/bridge/BridgeFactory.java
   search/trunk/src/java/org/hibernate/search/engine/DocumentBuilder.java
   search/trunk/src/test/org/hibernate/search/test/PurgeTest.java
Log:
HSEARCH-81 Add @ClassBridge support (John Griffin)

Added: search/trunk/src/java/org/hibernate/search/annotations/ClassBridge.java
===================================================================
--- search/trunk/src/java/org/hibernate/search/annotations/ClassBridge.java	                        (rev 0)
+++ search/trunk/src/java/org/hibernate/search/annotations/ClassBridge.java	2007-09-13 03:48:51 UTC (rev 14009)
@@ -0,0 +1,61 @@
+package org.hibernate.search.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation allows a user to apply an implementation
+ * class to a Lucene document to manipulate it in any way
+ * the usersees fit.
+ *
+ * @author John Griffin
+ */
+ at Retention( RetentionPolicy.RUNTIME )
+ at Target( ElementType.TYPE )
+ at Documented
+public @interface ClassBridge {
+	/**
+	 * Field name, default to the JavaBean property name.
+	 */
+	String name() default "";
+
+	/**
+	 * Should the value be stored in the document.
+	 * defaults to no.
+	 */
+	Store store() default Store.NO;
+
+	/**
+	 * Define an analyzer for the field, default to
+	 * the inherited analyzer.
+	 */
+	Analyzer analyzer() default @Analyzer;
+
+	/**
+	 * Defines how the Field should be indexed
+	 * defaults to tokenized.
+	 */
+	Index index() default Index.TOKENIZED;
+
+	/**
+	 * A float value of the amount of lucene defined
+	 * boost to apply to a field.
+	 */
+	Boost boost() default @Boost(value=1.0F);
+
+	/**
+	 * User supplied class to manipulate document in
+	 * whatever mysterious ways they wish to.
+	 */
+	public Class impl() default void.class;
+
+	/**
+	 * Array of fields to work with. The imnpl class
+	 * above will work on these fields.
+	 */
+	public Parameter[] params() default {};
+
+}
\ No newline at end of file

Modified: search/trunk/src/java/org/hibernate/search/bridge/BridgeFactory.java
===================================================================
--- search/trunk/src/java/org/hibernate/search/bridge/BridgeFactory.java	2007-09-09 10:41:51 UTC (rev 14008)
+++ search/trunk/src/java/org/hibernate/search/bridge/BridgeFactory.java	2007-09-13 03:48:51 UTC (rev 14009)
@@ -23,6 +23,7 @@
 import org.hibernate.search.annotations.Resolution;
 import org.hibernate.search.annotations.Parameter;
 import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.ClassBridge;
 import org.hibernate.search.SearchException;
 import org.hibernate.search.util.BinderHelper;
 import org.hibernate.annotations.common.reflection.XClass;
@@ -30,6 +31,7 @@
 
 /**
  * @author Emmanuel Bernard
+ * @author John Griffin
  */
 public class BridgeFactory {
 	private static Map<String, FieldBridge> builtInBridges = new HashMap<String, FieldBridge>();
@@ -83,7 +85,45 @@
 
 		builtInBridges.put( Date.class.getName(), DATE_MILLISECOND );
 	}
+	
+	/**
+	 * This extracts and instantiates the implementation class from a ClassBridge
+	 * annotation.
+	 *
+	 * @param cb the ClassBridge
+	 * @return FieldBridge
+	 */
+	public static FieldBridge extractType(ClassBridge cb)
+	{
+		FieldBridge bridge = null;
 
+		if ( cb != null ) {
+			Class impl = cb.impl();
+
+			if (impl != null) {
+				try {
+					Object instance = impl.newInstance();
+					if ( FieldBridge.class.isAssignableFrom( impl ) ) {
+						bridge = (FieldBridge) instance;
+					}
+					if ( cb.params().length > 0 && ParameterizedBridge.class.isAssignableFrom( impl ) ) {
+						Map params = new HashMap( cb.params().length );
+						for ( Parameter param : cb.params() ) {
+							params.put( param.name(), param.value() );
+						}
+						( (ParameterizedBridge) instance ).setParameterValues( params );
+					}
+				}
+				catch (Exception e) {
+					throw new HibernateException( "Unable to instantiate FieldBridge for " + ClassBridge.class.getName(), e );
+				}
+			}
+		}
+		if ( bridge == null ) throw new SearchException( "Unable to guess FieldBridge for " + ClassBridge.class.getName() );
+
+		return bridge;
+	}
+
 	public static FieldBridge guessType(Field field, XMember member) {
 		FieldBridge bridge = null;
 		org.hibernate.search.annotations.FieldBridge bridgeAnn;

Modified: search/trunk/src/java/org/hibernate/search/engine/DocumentBuilder.java
===================================================================
--- search/trunk/src/java/org/hibernate/search/engine/DocumentBuilder.java	2007-09-09 10:41:51 UTC (rev 14008)
+++ search/trunk/src/java/org/hibernate/search/engine/DocumentBuilder.java	2007-09-13 03:48:51 UTC (rev 14009)
@@ -33,6 +33,7 @@
 import org.hibernate.search.annotations.Index;
 import org.hibernate.search.annotations.IndexedEmbedded;
 import org.hibernate.search.annotations.Store;
+import org.hibernate.search.annotations.ClassBridge;
 import org.hibernate.search.backend.AddLuceneWork;
 import org.hibernate.search.backend.DeleteLuceneWork;
 import org.hibernate.search.backend.LuceneWork;
@@ -139,6 +140,11 @@
 			if ( analyzer != null ) {
 				propertiesMetadata.analyzer = analyzer;
 			}
+			// Check for any ClassBridge style of annotations.
+			ClassBridge classBridgeAnn = currClass.getAnnotation(ClassBridge.class);
+			if (classBridgeAnn != null) {
+				bindClassAnnotation(prefix, propertiesMetadata, classBridgeAnn);
+			}
 			//rejecting non properties because the object is loaded from Hibernate, so indexing a non property does not make sense
 			List<XProperty> methods = currClass.getDeclaredProperties( XClass.ACCESS_PROPERTY );
 			for (XProperty method : methods) {
@@ -279,6 +285,21 @@
 		}
 	}
 
+	private void bindClassAnnotation(String prefix, PropertiesMetadata propertiesMetadata, ClassBridge ann) {
+		//FIXME name should be prefixed
+		String fieldName = prefix + ann.name();
+		propertiesMetadata.classNames.add( fieldName );
+		propertiesMetadata.classStores.add( getStore( ann.store() ) );
+		propertiesMetadata.classIndexes.add( getIndex( ann.index() ) );
+		propertiesMetadata.classBridges.add( BridgeFactory.extractType( ann ) );
+		propertiesMetadata.classBoosts.add( ann.boost().value() );
+
+		Analyzer analyzer = getAnalyzer( ann.analyzer() );
+		if ( analyzer == null ) analyzer = propertiesMetadata.analyzer;
+		if ( analyzer == null ) throw new AssertionFailure( "Analyzer should not be undefined" );
+		this.analyzer.addScopedAnalyzer( fieldName, analyzer );
+	}
+
 	private void bindFieldAnnotation(XProperty member, PropertiesMetadata propertiesMetadata, String prefix, org.hibernate.search.annotations.Field fieldAnn) {
 		setAccessible( member );
 		propertiesMetadata.fieldGetters.add( member );
@@ -487,6 +508,16 @@
 		if ( instance == null ) return;
 		//needed for field access: I cannot work in the proxied version
 		Object unproxiedInstance = unproxy( instance );
+		for (int i = 0; i < propertiesMetadata.classBridges.size(); i++) {
+			FieldBridge fb = propertiesMetadata.classBridges.get( i );
+
+			fb.set( propertiesMetadata.classNames.get(i),
+					unproxiedInstance,
+					doc,
+					propertiesMetadata.classStores.get(i),
+					propertiesMetadata.classIndexes.get(i),
+					propertiesMetadata.classBoosts.get(i));
+		}
 		for (int i = 0; i < propertiesMetadata.fieldNames.size(); i++) {
 			XMember member = propertiesMetadata.fieldGetters.get( i );
 			Object value = getMemberValue( unproxiedInstance, member );
@@ -677,6 +708,11 @@
 		public final List<PropertiesMetadata> embeddedPropertiesMetadata = new ArrayList<PropertiesMetadata>();
 		public final List<Container> embeddedContainers = new ArrayList<Container>();
 		public final List<XMember> containedInGetters = new ArrayList<XMember>();
+		public final List<String> classNames = new ArrayList<String>();
+		public final List<Field.Store> classStores = new ArrayList<Field.Store>();
+		public final List<Field.Index> classIndexes = new ArrayList<Field.Index>();
+		public final List<FieldBridge> classBridges = new ArrayList<FieldBridge>();
+		public final List<Float> classBoosts = new ArrayList<Float>();
 
 		public enum Container {
 			OBJECT,

Modified: search/trunk/src/test/org/hibernate/search/test/PurgeTest.java
===================================================================
--- search/trunk/src/test/org/hibernate/search/test/PurgeTest.java	2007-09-09 10:41:51 UTC (rev 14008)
+++ search/trunk/src/test/org/hibernate/search/test/PurgeTest.java	2007-09-13 03:48:51 UTC (rev 14009)
@@ -88,11 +88,16 @@
 
 		tx = s.beginTransaction();
 
-		Query query = parser.parse( "brand:Festina or brand:Seiko" );
+		Query query = parser.parse( "brand:Festina or brand:Seiko or brand:Longine or brand:Rolex" );
 		org.hibernate.Query hibQuery = s.createFullTextQuery( query, Clock.class, Book.class );
 		List results = hibQuery.list();
-		assertEquals("incorrect test record count", 0, results.size());
+		assertEquals("class not completely purged", 0, results.size());
 
+		query = parser.parse( "summary:Festina or summary:gloire" );
+		hibQuery = s.createFullTextQuery( query, Clock.class, Book.class );
+		results = hibQuery.list();
+		assertEquals("incorrect class purged", 2, results.size());
+
 		for (Object element : s.createQuery( "from java.lang.Object" ).list()) s.delete( element );
 		tx.commit();
 		s.close();

Added: search/trunk/src/test/org/hibernate/search/test/bridge/CatFieldsClassBridge.java
===================================================================
--- search/trunk/src/test/org/hibernate/search/test/bridge/CatFieldsClassBridge.java	                        (rev 0)
+++ search/trunk/src/test/org/hibernate/search/test/bridge/CatFieldsClassBridge.java	2007-09-13 03:48:51 UTC (rev 14009)
@@ -0,0 +1,42 @@
+package org.hibernate.search.test.bridge;
+
+import java.util.Map;
+
+import org.hibernate.search.bridge.StringBridge;
+import org.hibernate.search.bridge.ParameterizedBridge;
+import org.hibernate.search.bridge.FieldBridge;
+import org.hibernate.util.StringHelper;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+
+/**
+ * @author John Griffin
+ */
+public class CatFieldsClassBridge implements FieldBridge, ParameterizedBridge {
+
+	private String sepChar;
+
+	public void setParameterValues(Map parameters) {
+		this.sepChar = (String) parameters.get( "sepChar" );
+	}
+
+	public void set(String name, Object value, Document document, Field.Store store, Field.Index index, Float boost) {
+		// In this particular class the name of the new field was passed
+		// from the name field of the ClassBridge Annotation. This is not
+		// a requirement. It just works that way in this instance. The
+		// actual name could be supplied by hard coding it below.
+		Department dep = (Department) value;
+		String fieldValue1 = dep.getBranch();
+		if ( fieldValue1 == null ) {
+			fieldValue1 = "";
+		}
+		String fieldValue2 = dep.getNetwork();
+		if ( fieldValue2 == null ) {
+			fieldValue2 = "";
+		}
+		String fieldValue = fieldValue1 + sepChar + fieldValue2;
+		Field field = new Field( name, fieldValue, store, index );
+		if ( boost != null ) field.setBoost( boost );
+		document.add( field );
+	}
+}
\ No newline at end of file

Added: search/trunk/src/test/org/hibernate/search/test/bridge/ClassBridgeTest.java
===================================================================
--- search/trunk/src/test/org/hibernate/search/test/bridge/ClassBridgeTest.java	                        (rev 0)
+++ search/trunk/src/test/org/hibernate/search/test/bridge/ClassBridgeTest.java	2007-09-13 03:48:51 UTC (rev 14009)
@@ -0,0 +1,129 @@
+package org.hibernate.search.test.bridge;
+
+import java.util.List;
+
+import org.apache.lucene.analysis.SimpleAnalyzer;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.search.Query;
+import org.hibernate.Transaction;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.search.Environment;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.search.Search;
+import org.hibernate.search.test.SearchTestCase;
+
+/**
+ * @author John Griffin
+ */
+public class ClassBridgeTest extends SearchTestCase {
+	/**
+	 * This test checks for two fields being concatentated by the user-supplied
+	 * CatFieldsClassBridge class which is specified as the implementation class
+	 * in the ClassBridge annotation of the Department class.
+	 *
+	 * @throws Exception
+	 */
+	public void testClassBridge() throws Exception {
+		org.hibernate.Session s = openSession();
+		Transaction tx = s.beginTransaction();
+		s.persist( getDept1() );
+		s.persist( getDept2() );
+		s.persist( getDept3() );
+		s.flush();
+		tx.commit();
+
+		tx = s.beginTransaction();
+		FullTextSession session = Search.createFullTextSession( s );
+
+		// The branchnetwork field is the concatenation of both
+		// the branch field and the network field of the Department
+		// class. This is in the Lucene document but not in the
+		// Department entity itself.
+		QueryParser parser = new QueryParser( "branchnetwork", new SimpleAnalyzer() );
+
+		Query query = parser.parse( "branchnetwork:layton 2B" );
+		org.hibernate.search.FullTextQuery hibQuery = session.createFullTextQuery( query, Department.class );
+		List result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( "incorrect entity returned, wrong network", "2B", ( (Department) result.get( 0 ) ).getNetwork() );
+		assertEquals( "incorrect entity returned, wrong branch", "Layton", ( (Department) result.get( 0 ) ).getBranch() );
+		assertEquals( "incorrect number of results returned", 1, result.size() );
+
+		// Partial match.
+		query = parser.parse( "branchnetwork:3c" );
+		hibQuery = session.createFullTextQuery( query, Department.class );
+		result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( "incorrect entity returned, wrong network", "3C", ( (Department) result.get( 0 ) ).getNetwork() );
+		assertEquals( "incorrect entity returned, wrong branch", "West Valley", ( (Department) result.get( 0 ) ).getBranch() );
+		assertEquals( "incorrect number of results returned", 1, result.size() );
+
+		// No data cross-ups .
+		query = parser.parse( "branchnetwork:Kent Lewin" );
+		hibQuery = session.createFullTextQuery( query, Department.class );
+		result = hibQuery.list();
+		assertNotNull( result );
+		assertTrue( "problem with field cross-ups", result.size() == 0 );
+
+		// Non-ClassBridge field.
+		parser = new QueryParser( "branchHead", new SimpleAnalyzer() );
+		query = parser.parse( "branchHead:Kent Lewin" );
+		hibQuery = session.createFullTextQuery( query, Department.class );
+		result = hibQuery.list();
+		assertNotNull( result );
+		assertTrue( "incorrect entity returned, wrong branch head", result.size() == 1 );
+		assertEquals("incorrect entity returned", "Kent Lewin", ( (Department) result.get( 0 ) ).getBranchHead());
+
+		//cleanup
+		for (Object element : s.createQuery( "from " + Department.class.getName() ).list()) s.delete( element );
+		tx.commit();
+		s.close();
+	}
+
+	private Department getDept1() {
+		Department dept = new Department();
+
+//		dept.setId( 1000 );
+		dept.setBranch( "Salt Lake City" );
+		dept.setBranchHead( "Kent Lewin" );
+		dept.setMaxEmployees( 100 );
+		dept.setNetwork( "1A" );
+
+		return dept;
+	}
+
+	private Department getDept2() {
+		Department dept = new Department();
+
+//		dept.setId( 1001 );
+		dept.setBranch( "Layton" );
+		dept.setBranchHead( "Terry Poperszky" );
+		dept.setMaxEmployees( 20 );
+		dept.setNetwork( "2B" );
+
+		return dept;
+	}
+
+	private Department getDept3() {
+		Department dept = new Department();
+
+//		dept.setId( 1002 );
+		dept.setBranch( "West Valley" );
+		dept.setBranchHead( "Pat Kelley" );
+		dept.setMaxEmployees( 15 );
+		dept.setNetwork( "3C" );
+
+		return dept;
+	}
+
+	protected Class[] getMappings() {
+		return new Class[] {
+				Department.class
+		};
+	}
+
+	protected void configure(Configuration cfg) {
+		super.configure( cfg );
+		cfg.setProperty( Environment.ANALYZER_CLASS, SimpleAnalyzer.class.getName() );
+	}
+}
\ No newline at end of file

Added: search/trunk/src/test/org/hibernate/search/test/bridge/Department.java
===================================================================
--- search/trunk/src/test/org/hibernate/search/test/bridge/Department.java	                        (rev 0)
+++ search/trunk/src/test/org/hibernate/search/test/bridge/Department.java	2007-09-13 03:48:51 UTC (rev 14009)
@@ -0,0 +1,79 @@
+package org.hibernate.search.test.bridge;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+import org.hibernate.search.annotations.ClassBridge;
+import org.hibernate.search.annotations.DocumentId;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Index;
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.Parameter;
+import org.hibernate.search.annotations.Store;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+
+/**
+ * @author John Griffin
+ */
+ at Entity
+ at Indexed
+ at ClassBridge(name="branchnetwork",
+			 index=Index.TOKENIZED,
+			 store=Store.YES,
+			 impl = CatFieldsClassBridge.class,
+			 params = @Parameter( name="sepChar", value=" " ) )
+public class Department {
+	private int id;
+	private String network;
+	private String branchHead;
+	private String branch;
+	private Integer maxEmployees;
+
+	@Id
+	@GeneratedValue
+	@DocumentId
+	public int getId() {
+		return id;
+	}
+
+	public void setId(int id) {
+		this.id = id;
+	}
+
+	@Field(index=Index.TOKENIZED, store=Store.YES)
+	public String getBranchHead() {
+		return branchHead;
+	}
+
+	public void setBranchHead(String branchHead) {
+		this.branchHead = branchHead;
+	}
+
+	@Field(index=Index.TOKENIZED, store=Store.YES)
+	public String getNetwork() {
+		return network;
+	}
+
+	public void setNetwork(String network) {
+		this.network = network;
+	}
+
+	@Field(index=Index.TOKENIZED, store=Store.YES)
+	public String getBranch() {
+		return branch;
+	}
+
+	public void setBranch(String branch) {
+		this.branch = branch;
+	}
+
+	@Field(index=Index.UN_TOKENIZED, store=Store.YES)
+	public Integer getMaxEmployees() {
+		return maxEmployees;
+	}
+
+	public void setMaxEmployees(Integer maxEmployees) {
+		this.maxEmployees = maxEmployees;
+	}
+}
\ No newline at end of file




More information about the hibernate-commits mailing list