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}; } |