Author: epbernard
Date: 2006-06-12 12:56:27 -0400 (Mon, 12 Jun 2006)
New Revision: 10014
Added:
trunk/HibernateExt/metadata/src/java/org/hibernate/lucene/Boost.java
Modified:
trunk/HibernateExt/metadata/src/java/org/hibernate/lucene/DocumentBuilder.java
trunk/HibernateExt/metadata/src/java/org/hibernate/lucene/event/LuceneEventListener.java
trunk/HibernateExt/metadata/src/test/org/hibernate/test/lucene/Document.java
trunk/HibernateExt/metadata/src/test/org/hibernate/test/lucene/LuceneTest.java
Log:
ANN-370 thread safe
ANN-371 abstract for the Directory implementation
ANN-372 @Boost
Added: trunk/HibernateExt/metadata/src/java/org/hibernate/lucene/Boost.java
===================================================================
--- trunk/HibernateExt/metadata/src/java/org/hibernate/lucene/Boost.java 2006-06-11 17:00:19 UTC (rev 10013)
+++ trunk/HibernateExt/metadata/src/java/org/hibernate/lucene/Boost.java 2006-06-12 16:56:27 UTC (rev 10014)
@@ -0,0 +1,20 @@
+//$Id: $
+package org.hibernate.lucene;
+
+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;
+
+/**
+ * Apply a boost factor on a field or a whole entity
+ *
+ * @author Emmanuel Bernard
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Documented
+public @interface Boost {
+ float value();
+}
Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/lucene/DocumentBuilder.java
===================================================================
--- trunk/HibernateExt/metadata/src/java/org/hibernate/lucene/DocumentBuilder.java 2006-06-11 17:00:19 UTC (rev 10013)
+++ trunk/HibernateExt/metadata/src/java/org/hibernate/lucene/DocumentBuilder.java 2006-06-12 16:56:27 UTC (rev 10014)
@@ -1,20 +1,21 @@
//$Id$
package org.hibernate.lucene;
-import java.io.File;
+import java.beans.Introspector;
import java.io.Serializable;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
+import java.lang.reflect.AnnotatedElement;
import java.util.ArrayList;
import java.util.List;
-import java.beans.Introspector;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.Term;
+import org.apache.lucene.store.Directory;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.cfg.annotations.Version;
@@ -34,15 +35,15 @@
private final List<String> textNames = new ArrayList<String>();
//private final Class<T> beanClass;
- private final File file;
+ private final Directory directory;
private String idKeywordName;
private final Analyzer analyzer;
+ private Float idBoost;
- public DocumentBuilder(Class<?> clazz, Analyzer analyzer, File indexDir) {
+ public DocumentBuilder(Class<?> clazz, Analyzer analyzer, Directory directory) {
//this.beanClass = clazz;
this.analyzer = analyzer;
- String fileName = getTypeName( clazz, ( clazz.getAnnotation( Indexed.class ) ).index() );
- file = new File( indexDir, fileName );
+ this.directory = directory;
for ( Class currClass = clazz; currClass != null ; currClass = currClass.getSuperclass() ) {
Method[] methods = currClass.getDeclaredMethods();
@@ -53,6 +54,7 @@
String name = getAttributeName( method, keywordAnn.name() );
if ( keywordAnn.id() ) {
idKeywordName = name;
+ idBoost = getBoost( method );
}
else {
setAccessible( method );
@@ -79,6 +81,12 @@
}
}
+ private Float getBoost(AnnotatedElement element) {
+ if (element == null) return null;
+ Boost boost = element.getAnnotation( Boost.class );
+ return boost != null ? new Float( boost.value() ) : null;
+ }
+
private Object getValue(Member member, T bean) {
try {
if ( member instanceof java.lang.reflect.Field ) {
@@ -98,30 +106,53 @@
public Document getDocument(T instance, Serializable id) {
Document doc = new Document();
- doc.add( new Field( idKeywordName, id.toString(), Field.Store.YES, Field.Index.UN_TOKENIZED ) );
+ Float boost = getBoost( instance.getClass() );
+ if (boost != null) {
+ doc.setBoost( boost.floatValue() );
+ }
+ {
+ Field idField = new Field( idKeywordName, id.toString(), Field.Store.YES, Field.Index.UN_TOKENIZED );
+ if (idBoost != null) {
+ idField.setBoost( idBoost.floatValue() );
+ }
+ doc.add( idField );
+ }
for ( int i = 0; i < keywordNames.size() ; i++ ) {
Member member = keywordGetters.get( i );
Object value = getValue( member, instance );
if ( value != null ) {
- doc.add( new Field( keywordNames.get( i ), toString( value ), Field.Store.YES, Field.Index.UN_TOKENIZED ) );
+ Field field = new Field( keywordNames.get( i ), toString( value ), Field.Store.YES, Field.Index.UN_TOKENIZED );
+ boostField(field, member);
+ doc.add( field );
}
}
for ( int i = 0; i < textNames.size() ; i++ ) {
- Object value = getValue( textGetters.get( i ), instance );
+ Member member = textGetters.get( i );
+ Object value = getValue( member, instance );
if ( value != null ) {
- doc.add( new Field( textNames.get( i ), toString( value ), Field.Store.YES, Field.Index.TOKENIZED) );
+ Field field = new Field( textNames.get( i ), toString( value ), Field.Store.YES, Field.Index.TOKENIZED );
+ boostField(field, member);
+ doc.add( field );
}
}
for ( int i = 0; i < unstoredNames.size() ; i++ ) {
- Object value = getValue( unstoredGetters.get( i ), instance );
+ Member member = unstoredGetters.get( i );
+ Object value = getValue( member, instance );
if ( value != null ) {
- doc.add( new Field( unstoredNames.get( i ), toString( value ), Field.Store.NO, Field.Index.TOKENIZED ) );
+ Field field = new Field( unstoredNames.get( i ), toString( value ), Field.Store.NO, Field.Index.TOKENIZED );
+ boostField(field, member);
+ doc.add( field );
}
}
return doc;
}
+ private void boostField(Field field, Member member) {
+ Float boost = getBoost( (AnnotatedElement) member );
+ if (boost != null) field.setBoost( boost.floatValue() );
+ }
+
private static String toString(Object value) {
return value.toString();
}
@@ -143,14 +174,10 @@
return Introspector.decapitalize( methodName.substring( startIndex ) );
}
- private static String getTypeName(Class clazz, String name) {
- return "".equals( name ) ? clazz.getName() : name;
+ public Directory getDirectory() {
+ return directory;
}
- public File getFile() {
- return file;
- }
-
public Analyzer getAnalyzer() {
return analyzer;
}
Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/lucene/event/LuceneEventListener.java
===================================================================
--- trunk/HibernateExt/metadata/src/java/org/hibernate/lucene/event/LuceneEventListener.java 2006-06-11 17:00:19 UTC (rev 10013)
+++ trunk/HibernateExt/metadata/src/java/org/hibernate/lucene/event/LuceneEventListener.java 2006-06-12 16:56:27 UTC (rev 10014)
@@ -7,6 +7,8 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -16,6 +18,8 @@
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
import org.hibernate.HibernateException;
import org.hibernate.cfg.Configuration;
import org.hibernate.event.Initializable;
@@ -38,10 +42,13 @@
* @author Emmanuel Bernard
* @author Mattias Arbin
*/
+//TODO takes care of synchronization and index concurrent index change
public class LuceneEventListener implements PostDeleteEventListener, PostInsertEventListener,
PostUpdateEventListener, Initializable {
private Map<Class, DocumentBuilder<Object>> documentBuilders = new HashMap<Class, DocumentBuilder<Object>>();
+ //** keep track of the index modifiers per file since 1 index modifier can be present at a time */
+ private Map<Directory, Lock> indexLock = new HashMap<Directory, Lock>();
private boolean initialized;
private static final Log log = LogFactory.getLog( LuceneEventListener.class );
@@ -92,8 +99,26 @@
PersistentClass clazz = (PersistentClass) iter.next();
Class<?> mappedClass = clazz.getMappedClass();
if ( mappedClass != null ) {
- if ( mappedClass.getAnnotation( Indexed.class ) != null ) {
- final DocumentBuilder<Object> documentBuilder = new DocumentBuilder<Object>( mappedClass, analyzer, indexDir );
+ if ( mappedClass.isAnnotationPresent( Indexed.class ) ) {
+ Indexed indexed = mappedClass.getAnnotation( Indexed.class );
+ String fileName = getTypeName( mappedClass, indexed.index() );
+ File file = new File( indexDir, fileName );
+ Directory directory;
+ try {
+ boolean create = !file.exists();
+ directory = FSDirectory.getDirectory( file.getCanonicalPath(), create );
+ if (create) {
+ IndexWriter iw = new IndexWriter(directory, new StandardAnalyzer(), create );
+ iw.close();
+ }
+ }
+ catch (IOException ie) {
+ throw new HibernateException("Unable to initialize index: " + indexed.index(), ie );
+ }
+ final DocumentBuilder<Object> documentBuilder = new DocumentBuilder<Object>( mappedClass, analyzer, directory );
+ if ( ! indexLock.containsKey( directory ) ) {
+ indexLock.put( directory, new ReentrantLock() );
+ }
documentBuilders.put( mappedClass, documentBuilder );
// try {
// IndexWriter iw = new IndexWriter( documentBuilder.getFile(), new StopAnalyzer(), true );
@@ -102,13 +127,17 @@
// catch (IOException ioe) {
// throw new HibernateException(ioe);
// }
- log.info( "index: " + documentBuilder.getFile().getAbsolutePath() );
+ log.info( "index file: " + file.getAbsolutePath() );
}
}
}
initialized = true;
}
+ private static String getTypeName(Class clazz, String name) {
+ return "".equals( name ) ? clazz.getName() : name;
+ }
+
public void onPostDelete(PostDeleteEvent event) {
DocumentBuilder builder = documentBuilders.get( event.getEntity().getClass() );
if ( builder != null ) {
@@ -137,14 +166,21 @@
private void remove(DocumentBuilder<?> builder, Serializable id) {
Term term = builder.getTerm( id );
log.debug( "removing: " + term );
+ Directory directory = builder.getDirectory();
+ Lock lock = indexLock.get( directory );
+ lock.lock();
try {
- IndexReader reader = IndexReader.open( builder.getFile() );
+
+ IndexReader reader = IndexReader.open( directory );
reader.deleteDocuments( term );
reader.close();
}
catch (IOException ioe) {
throw new HibernateException( ioe );
}
+ finally {
+ lock.unlock();
+ }
}
private void add(final Object entity, final DocumentBuilder<Object> builder, final Serializable id) {
@@ -152,15 +188,20 @@
if( log.isDebugEnabled() ) {
log.debug( "adding: " + doc );
}
+ Directory directory = builder.getDirectory();
+ Lock lock = indexLock.get( directory );
+ lock.lock();
try {
- File file = builder.getFile();
- IndexWriter writer = new IndexWriter( file, builder.getAnalyzer(), ! file.exists() );
+ IndexWriter writer = new IndexWriter( directory, builder.getAnalyzer(), false); //have been created at init time
writer.addDocument( doc );
writer.close();
}
catch (IOException ioe) {
throw new HibernateException( ioe );
}
+ finally {
+ lock.unlock();
+ }
}
}
Modified: trunk/HibernateExt/metadata/src/test/org/hibernate/test/lucene/Document.java
===================================================================
--- trunk/HibernateExt/metadata/src/test/org/hibernate/test/lucene/Document.java 2006-06-11 17:00:19 UTC (rev 10013)
+++ trunk/HibernateExt/metadata/src/test/org/hibernate/test/lucene/Document.java 2006-06-12 16:56:27 UTC (rev 10014)
@@ -10,6 +10,7 @@
import org.hibernate.lucene.Keyword;
import org.hibernate.lucene.Text;
import org.hibernate.lucene.Unstored;
+import org.hibernate.lucene.Boost;
@Entity
@Indexed(index = "Documents")
@@ -41,6 +42,7 @@
}
@Text
+ @Boost(2)
public String getTitle() {
return title;
}
Modified: trunk/HibernateExt/metadata/src/test/org/hibernate/test/lucene/LuceneTest.java
===================================================================
--- trunk/HibernateExt/metadata/src/test/org/hibernate/test/lucene/LuceneTest.java 2006-06-11 17:00:19 UTC (rev 10013)
+++ trunk/HibernateExt/metadata/src/test/org/hibernate/test/lucene/LuceneTest.java 2006-06-12 16:56:27 UTC (rev 10014)
@@ -2,13 +2,18 @@
package org.hibernate.test.lucene;
import java.io.File;
+import java.util.List;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.apache.lucene.analysis.StopAnalyzer;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Hits;
import org.hibernate.Session;
import org.hibernate.cfg.Configuration;
import org.hibernate.event.PostDeleteEventListener;
@@ -70,16 +75,20 @@
s.close();
IndexReader reader = IndexReader.open( new File( getBaseIndexDir(), "Documents" ) );
- int num = reader.numDocs();
- assertEquals( 1, num );
- TermDocs docs = reader.termDocs( new Term( "Abstract", "Hibernate" ) );
- org.apache.lucene.document.Document doc = reader.document( docs.doc() );
- assertFalse( docs.next() );
- docs = reader.termDocs( new Term( "Title", "Action" ) );
- doc = reader.document( docs.doc() );
- assertFalse( docs.next() );
- assertEquals( "1", doc.getField( "id" ).stringValue() );
- reader.close();
+ try {
+ int num = reader.numDocs();
+ assertEquals( 1, num );
+ TermDocs docs = reader.termDocs( new Term( "Abstract", "Hibernate" ) );
+ org.apache.lucene.document.Document doc = reader.document( docs.doc() );
+ assertFalse( docs.next() );
+ docs = reader.termDocs( new Term( "Title", "Action" ) );
+ doc = reader.document( docs.doc() );
+ assertFalse( docs.next() );
+ assertEquals( "1", doc.getField( "id" ).stringValue() );
+ }
+ finally {
+ reader.close();
+ }
s = getSessions().openSession();
s.getTransaction().begin();
@@ -90,12 +99,16 @@
s.close();
reader = IndexReader.open( new File( getBaseIndexDir(), "Documents" ) );
- num = reader.numDocs();
- assertEquals( 2, num );
- docs = reader.termDocs( new Term( "Abstract", "EJB3" ) );
- doc = reader.document( docs.doc() );
- assertFalse( docs.next() );
- reader.close();
+ try {
+ int num = reader.numDocs();
+ assertEquals( 2, num );
+ TermDocs docs = reader.termDocs( new Term( "Abstract", "EJB3" ) );
+ org.apache.lucene.document.Document doc = reader.document( docs.doc() );
+ assertFalse( docs.next() );
+ }
+ finally {
+ reader.close();
+ }
s = getSessions().openSession();
s.getTransaction().begin();
@@ -104,13 +117,17 @@
s.close();
reader = IndexReader.open( new File( getBaseIndexDir(), "Documents" ) );
- num = reader.numDocs();
- assertEquals( 1, num );
- docs = reader.termDocs( new Term( "Title", "Seam" ) );
- doc = reader.document( docs.doc() );
- assertFalse( docs.next() );
- assertEquals( "2", doc.getField( "id" ).stringValue() );
- reader.close();
+ try {
+ int num = reader.numDocs();
+ assertEquals( 1, num );
+ TermDocs docs = reader.termDocs( new Term( "Title", "Seam" ) );
+ org.apache.lucene.document.Document doc = reader.document( docs.doc() );
+ assertFalse( docs.next() );
+ assertEquals( "2", doc.getField( "id" ).stringValue() );
+ }
+ finally {
+ reader.close();
+ }
s = getSessions().openSession();
s.getTransaction().begin();
@@ -119,6 +136,43 @@
s.close();
}
+ public void testBoost() throws Exception {
+
+
+ Session s = getSessions().openSession();
+ s.getTransaction().begin();
+ s.persist(
+ new Document( "Hibernate in Action", "Object and Relational", "blah blah blah" )
+ );
+ s.persist(
+ new Document( "Object and Relational", "Hibernate in Action", "blah blah blah" )
+ );
+ s.getTransaction().commit();
+ s.close();
+
+ IndexSearcher searcher = new IndexSearcher( new File( getBaseIndexDir(), "Documents" ).getCanonicalPath() );
+ try {
+ QueryParser qp = new QueryParser("id", new StandardAnalyzer() );
+ Hits hits = searcher.search( qp.parse("title:Action OR Abstract:Action") );
+ assertEquals( 2, hits.length() );
+ assertTrue( hits.score( 0 ) == 2*hits.score( 1 ) );
+ assertEquals( "Hibernate in Action", hits.doc(0).get( "title") );
+ }
+ finally {
+ if (searcher != null) searcher.close();
+ }
+
+
+ s = getSessions().openSession();
+ s.getTransaction().begin();
+ List list = s.createQuery( "from Document" ).list();
+ for (Document document : (List<Document>) list) {
+ s.delete( document );
+ }
+ s.getTransaction().commit();
+ s.close();
+ }
+
protected Class[] getMappings() {
return new Class[]{Document.class};
}
|