[hibernate-dev] Transactional Lucene index

Emmanuel Bernard emmanuel at hibernate.org
Fri Oct 13 11:14:20 EDT 2006


Some people asked me whether or not we expect to add transactional 
support for Lucene operations.
So far, I've been able to convince them that this is not what they want, 
but I'd like to hear from you guys.

What Hibernate Lucene currently does is, straight after the transaction 
commit (DB), apply all the changes to the index.

I've having hard time to find a good use case for it. Especially because 
Index change is all about performance trades offs. And do you really 
want your commercial transaction rollback because your index has fail?

What I expect to add is a better implementation of what's currently 
happening. Especially, I want to batch all operations for a single 
transaction using the same DirectoryProvider I/O (there are several 
currently). But this is a speed improvement, not a behavioral change.
I'm also considering  adding an asynchronous batch mode. but that's mode 
a mid term work.

WDYT?

Richard Hallier wrote:
>
> Oui oui j'avais bien compris, je plaisantais :-)
> J'avais une autre question tant que j'y étais, par rapport à la gestion
> transactionnelle des opérations Lucene, y a t il quelque chose de prévu ?
>
> > -----Message d'origine-----
> > De : Emmanuel Bernard [mailto:emmanuel.bernard at jboss.com]
> > Envoyé : vendredi 13 octobre 2006 16:00
> > À : Richard Hallier
> > Objet : Re: Hibernate Lucene & fields
> >
> > je parlais des Bridges pour toutes les propriétés
> >
> > Richard Hallier wrote:
> > >
> > > Développé dans l'avion? J'espère que tu as fait un vol long
> > courrier
> > > ;-) Tiens moi au courant pour le patch.
> > > Richard
> > >
> > > > -----Message d'origine-----
> > > > De : Emmanuel Bernard [mailto:emmanuel.bernard at jboss.com]
> > > > Envoyé : vendredi 13 octobre 2006 01:36 À : Richard
> > Hallier Objet :
> > > > Re: Hibernate Lucene & fields
> > > >
> > > > Si, je viens de le développer dans l'avion aujourd'hui ;-) Merci
> > > > pour le patch je vais regarder ca demain
> > > >
> > > > Richard Hallier wrote:
> > > > >
> > > > > Salut Emmanuel,
> > > > > Tu trouveras le patch en question en pièce jointe.
> > > > > J'ai rajouté une classe de test plutot sommaire, cela
> > > > peut-etre amélioré.
> > > > >
> > > > > J'ai une question. J'ai vu que la conversion (hors identifiant)
> > > > > des properties des entités en valeur lucene se fait par
> > une simple
> > > > > conversion texte, alors que tu as mis en place pour la
> > gestion de
> > > > > l'identifiant un bridge. Ce concept de "converter" ne
> > > > pourrait il pas
> > > > > etre appliqué à toute conversion (pas seulement l'id) en
> > > > valeur Lucene?
> > > > >
> > > > > Richard.
> > > > >
> > > > > > -----Message d'origine-----
> > > > > > De : Emmanuel Bernard [mailto:emmanuel at hibernate.org] Envoyé :
> > > > > > mercredi 11 octobre 2006 05:24 À :
> > > > richard.hallier at freesbee.fr Objet
> > > > > > : Hibernate Lucene & fields
> > > > > >
> > > > > > Hi,
> > > > > > I bet you're French so I'll switch to it.
> > > > > >
> > > > > > J'ai pensé au code qui permettrait d'annoter les attributs.
> > > > > > En fait je voudrais utiliser une abstraction que l'on a
> > > > écrit pour
> > > > > > Hibernate Annotations. Cela abstrait le code de la résolution
> > > > > > Generics de Java 5 et des propriétés vs attributs.
> > > > > >
> > > > > > Quand tu commences le patch, regarde
> > > > > > org.hibernate.validator.ClassValidator, les accès aux
> > > > annotations se
> > > > > > font via  ReflectionManager reflectionManager = new
> > > > JavaXFactory();
> > > > > > dans la methode
> > > > > > ClassValidator.initValidator(...)
> > > > > >
> > > > > > Si tu as des questions, n'hésites pas à me demander.
> > > > > >
> > > > > > Emmanuel
> > > > > >
> > > > >
> > > > >
> > > >
> > --------------------------------------------------------------------
> > > > --
> > > > > --
> > > > >
> > > > > Index:
> > > > >
> > > >
> > C:/dev/source/workspace1.5/HibernateExt/metadata/src/test/org/hibern
> > > > at
> > > > > e/lucene/test/FieldMappingTest.java
> > > > >
> > ==================================================================
> > > > > =
> > > > > ---
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/test/org/
> > > > hibernate/lucene/test/FieldMappingTest.java   (revision 0)
> > > > > +++
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/test/org/
> > > > hibernate/lucene/test/FieldMappingTest.java   (revision 0)
> > > > > @@ -0,0 +1,191 @@
> > > > > +//$Id: LuceneTest.java 10014 2006-06-12 09:56:27 -0700
> > > > (lun., 12 juin
> > > > > +2006) epbernard $ package org.hibernate.lucene.test;
> > > > > +
> > > > > +import java.io.File;
> > > > > +import java.util.List;
> > > > > +
> > > > > +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.Hits; import
> > > > > +org.apache.lucene.search.IndexSearcher;
> > > > > +import org.apache.lucene.search.Query; import
> > > > org.hibernate.Session;
> > > > > +import org.hibernate.event.PostDeleteEventListener;
> > > > > +import org.hibernate.event.PostInsertEventListener;
> > > > > +import org.hibernate.event.PostUpdateEventListener;
> > > > > +import org.hibernate.lucene.Environment; import
> > > > > +org.hibernate.lucene.store.FSDirectoryProvider;
> > > > > +import org.hibernate.lucene.event.LuceneEventListener;
> > > > > +
> > > > > +/**
> > > > > + * @author Richard Hallier
> > > > > + */
> > > > > +public class FieldMappingTest extends TestCase {
> > > > > +
> > > > > +
> > > > > +   protected void setUp() throws Exception {
> > > > > +           File sub = getBaseIndexDir();
> > > > > +           sub.mkdir();
> > > > > +           File[] files = sub.listFiles();
> > > > > +           for (File file : files) {
> > > > > +                   if ( file.isDirectory() ) {
> > > > > +                           delete( file );
> > > > > +                   }
> > > > > +           }
> > > > > +           //super.setUp(); //we need a fresh session
> > > > factory each time for index set up
> > > > > +           buildSessionFactory( getMappings(),
> > > > getAnnotatedPackages(), getXmlFiles() );
> > > > > +   }
> > > > > +
> > > > > +   private File getBaseIndexDir() {
> > > > > +           File current = new File( "." );
> > > > > +           File sub = new File( current, "indextemp" );
> > > > > +           return sub;
> > > > > +   }
> > > > > +
> > > > > +   protected void tearDown() throws Exception {
> > > > > +           super.tearDown();
> > > > > +           File sub = getBaseIndexDir();
> > > > > +           delete( sub );
> > > > > +   }
> > > > > +
> > > > > +   private void delete(File sub) {
> > > > > +           if ( sub.isDirectory() ) {
> > > > > +                   for ( File file : sub.listFiles() ) {
> > > > > +                           delete( file );
> > > > > +                   }
> > > > > +                   sub.delete();
> > > > > +           }
> > > > > +           else {
> > > > > +                   sub.delete();
> > > > > +           }
> > > > > +   }
> > > > > +
> > > > > +   public void testEventIntegration() throws Exception {
> > > > > +
> > > > > +
> > > > > +           Session s = getSessions().openSession();
> > > > > +           s.getTransaction().begin();
> > > > > +           s.persist(
> > > > > +                           new Document2( "Hibernate in
> > > > Action", "Object/relational mapping with Hibernate", "blah blah
> > > > blah" )
> > > > > +           );
> > > > > +           s.getTransaction().commit();
> > > > > +           s.close();
> > > > > +           IndexReader reader = IndexReader.open( new
> > > > File( getBaseIndexDir(), "Documents" ) );
> > > > > +           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();
> > > > > +           Document2 entity = (Document2) s.get(
> > > > Document2.class, new Long( 1 ) );
> > > > > +           entity.setSummary( "Object/relational mapping
> > > > with EJB3" );
> > > > > +           s.persist( new Document2( "Seam in Action", "",
> > > > "blah blah blah blah" ) );
> > > > > +           s.getTransaction().commit();
> > > > > +           s.close();
> > > > > +
> > > > > +           reader = IndexReader.open( new File(
> > > > getBaseIndexDir(), "Documents" ) );
> > > > > +           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();
> > > > > +           s.delete( entity );
> > > > > +           s.getTransaction().commit();
> > > > > +           s.close();
> > > > > +
> > > > > +           reader = IndexReader.open( new File(
> > > > getBaseIndexDir(), "Documents" ) );
> > > > > +           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();
> > > > > +           s.delete( s.createCriteria( Document2.class
> > > > ).uniqueResult() );
> > > > > +           s.getTransaction().commit();
> > > > > +           s.close();
> > > > > +   }
> > > > > +
> > > > > +   public void testBoost() throws Exception {
> > > > > +           Session s = getSessions().openSession();
> > > > > +           s.getTransaction().begin();
> > > > > +           s.persist(
> > > > > +                           new Document2( "Hibernate in
> > > > Action", "Object and Relational", "blah blah blah" )
> > > > > +           );
> > > > > +           s.persist(
> > > > > +                           new Document2( "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() );
> > > > > +                   Query query = qp.parse( "title:Action
> > > > OR Abstract:Action" );
> > > > > +                   Hits hits = searcher.search( query );
> > > > > +                   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 Document2"
> > ).list();
> > > > > +           for ( Document2 document :
> > (List<Document2>) list ) {
> > > > > +                   s.delete( document );
> > > > > +           }
> > > > > +           s.getTransaction().commit();
> > > > > +           s.close();
> > > > > +   }
> > > > > +
> > > > > +   protected Class[] getMappings() {
> > > > > +           return new Class[]{Document2.class};
> > > > > +   }
> > > > > +
> > > > > +   protected void
> > configure(org.hibernate.cfg.Configuration cfg) {
> > > > > +           File sub = getBaseIndexDir();
> > > > > +           cfg.setProperty(
> > > > "hibernate.lucene.default.indexBase", sub.getAbsolutePath() );
> > > > > +           cfg.setProperty(
> > > > "hibernate.lucene.Clock.directory_provider",
> > > > FSDirectoryProvider.class.getName() );
> > > > > +           cfg.setProperty( Environment.ANALYZER_CLASS,
> > > > StopAnalyzer.class.getName() );
> > > > > +           LuceneEventListener del = new LuceneEventListener();
> > > > > +         
> > > > cfg.getEventListeners().setPostCommitDeleteEventListeners(
> > > > new PostDeleteEventListener[]{del} );
> > > > > +         
> > > > cfg.getEventListeners().setPostCommitUpdateEventListeners(
> > > > new PostUpdateEventListener[]{del} );
> > > > > +         
> > > > cfg.getEventListeners().setPostCommitInsertEventListeners(
> > > > new PostInsertEventListener[]{del} );
> > > > > +   }
> > > > > +
> > > > > +}
> > > > > +
> > > > > Index:
> > > > >
> > > >
> > C:/dev/source/workspace1.5/HibernateExt/metadata/src/test/org/hibern
> > > > at
> > > > > e/lucene/test/Document2.java
> > > > >
> > ==================================================================
> > > > > =
> > > > > ---
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/test/org/
> > > > hibernate/lucene/test/Document2.java  (revision 0)
> > > > > +++
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/test/org/
> > > > hibernate/lucene/test/Document2.java  (revision 0)
> > > > > @@ -0,0 +1,75 @@
> > > > > +//$Id: Document.java 10566 2006-10-11 04:01:11Z epbernard
> > > > $ package
> > > > > +org.hibernate.lucene.test;
> > > > > +
> > > > > +import javax.persistence.Entity;
> > > > > +import javax.persistence.GeneratedValue; import
> > > > javax.persistence.Id;
> > > > > +import javax.persistence.Lob;
> > > > > +
> > > > > +import org.hibernate.lucene.Indexed; import
> > > > > +org.hibernate.lucene.Keyword; import
> > org.hibernate.lucene.Text;
> > > > > +import org.hibernate.lucene.Unstored; import
> > > > > +org.hibernate.lucene.Boost;
> > > > > +
> > > > > + at Entity
> > > > > + at Indexed(index = "Documents")
> > > > > +public class Document2 {
> > > > > +   @Id
> > > > > +   @GeneratedValue
> > > > > +   @Keyword(id = true)
> > > > > +   private Long id;
> > > > > +
> > > > > +   @Text
> > > > > +   @Boost(2)
> > > > > +   private String title;
> > > > > + 
> > > > > +   @Unstored(name = "Abstract")
> > > > > +   private String summary;
> > > > > +
> > > > > +   @Lob
> > > > > +   @Unstored
> > > > > +   private String text;
> > > > > +
> > > > > +   Document2() {
> > > > > +   }
> > > > > +
> > > > > +   public Document2(String title, String summary,
> > String text) {
> > > > > +           super();
> > > > > +           this.summary = summary;
> > > > > +           this.text = text;
> > > > > +           this.title = title;
> > > > > +   }
> > > > > +
> > > > > +   public Long getId() {
> > > > > +           return id;
> > > > > +   }
> > > > > +
> > > > > +   public void setId(Long id) {
> > > > > +           this.id = id;
> > > > > +   }
> > > > > +
> > > > > +   public String getTitle() {
> > > > > +           return title;
> > > > > +   }
> > > > > +
> > > > > +   public void setTitle(String title) {
> > > > > +           this.title = title;
> > > > > +   }
> > > > > +
> > > > > +   public String getSummary() {
> > > > > +           return summary;
> > > > > +   }
> > > > > +
> > > > > +   public void setSummary(String summary) {
> > > > > +           this.summary = summary;
> > > > > +   }
> > > > > +
> > > > > +   public String getText() {
> > > > > +           return text;
> > > > > +   }
> > > > > +
> > > > > +   public void setText(String text) {
> > > > > +           this.text = text;
> > > > > +   }
> > > > > +}
> > > > > Index:
> > > > >
> > > >
> > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/hibern
> > > > at
> > > > > e/lucene/event/LuceneEventListener.java
> > > > >
> > ==================================================================
> > > > > =
> > > > > ---
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/
> > > > hibernate/lucene/event/LuceneEventListener.java      
> > (revision 10571)
> > > > > +++
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/
> > > > hibernate/lucene/event/LuceneEventListener.java      
> > (working copy)
> > > > > @@ -99,13 +99,10 @@
> > > > >              PersistentClass clazz = (PersistentClass)
> > iter.next();
> > > > >              Class<?> mappedClass = clazz.getMappedClass();
> > > > >              if (mappedClass != null) {
> > > > > -                if
> > > > (mappedClass.isAnnotationPresent(Indexed.class)) {
> > > > > -                    DirectoryProvider provider =
> > > > factory.createDirectoryProvider(mappedClass, cfg);
> > > > > -                    final DocumentBuilder<Object>
> > > > documentBuilder = new DocumentBuilder<Object>(
> > > > > -                            (Class<Object>) mappedClass,
> > > > analyzer, provider
> > > > > -                    );
> > > > > -                    if (!indexLock.containsKey(provider)) {
> > > > > -                        indexLock.put(provider, new
> > > > ReentrantLock());
> > > > > +                   final DocumentBuilder documentBuilder =
> > > > DocumentBuilder.createDocumentBuilder(mappedClass, analyzer,
> > > > factory, cfg);
> > > > > +                if(documentBuilder!=null) {
> > > > > +                    if
> > > > (!indexLock.containsKey(documentBuilder.getDirectoryProvider())) {
> > > > > +                      
> > > > > + indexLock.put(documentBuilder.getDirectoryProvider(), new
> > > > > + ReentrantLock());
> > > > >                      }
> > > > >                      documentBuilders.put(mappedClass,
> > > > documentBuilder);
> > > > >                  }
> > > > > Index:
> > > > >
> > > >
> > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/hibern
> > > > at
> > > > > e/lucene/Unstored.java
> > > > >
> > ==================================================================
> > > > > =
> > > > > ---
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/
> > > > hibernate/lucene/Unstored.java        (revision 10571)
> > > > > +++
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/
> > > > hibernate/lucene/Unstored.java        (working copy)
> > > > > @@ -8,7 +8,7 @@
> > > > >  import java.lang.annotation.Target;
> > > > >
> > > > >  @Retention(RetentionPolicy.RUNTIME)
> > > > > - at Target(ElementType.METHOD)
> > > > > + at Target({ElementType.METHOD, ElementType.FIELD})
> > > > >  @Documented
> > > > >  /**
> > > > >   * Specifies that a property of an entity is a Lucene
> > > > > Index:
> > > > >
> > > >
> > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/hibern
> > > > at
> > > > > e/lucene/Keyword.java
> > > > >
> > ==================================================================
> > > > > =
> > > > > ---
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/
> > > > hibernate/lucene/Keyword.java (revision 10571)
> > > > > +++
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/
> > > > hibernate/lucene/Keyword.java (working copy)
> > > > > @@ -8,7 +8,7 @@
> > > > >  import java.lang.annotation.Target;
> > > > >
> > > > >  @Retention(RetentionPolicy.RUNTIME)
> > > > > - at Target(ElementType.METHOD)
> > > > > + at Target({ElementType.METHOD, ElementType.FIELD})
> > > > >  @Documented
> > > > >  /**
> > > > >   * Specifies that a property of an entity is a Lucene
> > > > > Index:
> > > > >
> > > >
> > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/hibern
> > > > at
> > > > > e/lucene/Text.java
> > > > >
> > ==================================================================
> > > > > =
> > > > > ---
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/
> > > > hibernate/lucene/Text.java    (revision 10571)
> > > > > +++
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/
> > > > hibernate/lucene/Text.java    (working copy)
> > > > > @@ -8,7 +8,7 @@
> > > > >  import java.lang.annotation.Target;
> > > > >
> > > > >  @Retention(RetentionPolicy.RUNTIME)
> > > > > - at Target(ElementType.METHOD)
> > > > > + at Target({ElementType.METHOD, ElementType.FIELD})
> > > > >  @Documented
> > > > >  /**
> > > > >   * Specifies that a property of an entity is a Lucene
> > > > > Index:
> > > > >
> > > >
> > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/hibern
> > > > at
> > > > > e/lucene/DocumentBuilder.java
> > > > >
> > ==================================================================
> > > > > =
> > > > > ---
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/
> > > > hibernate/lucene/DocumentBuilder.java (revision 10571)
> > > > > +++
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/
> > > > hibernate/lucene/DocumentBuilder.java (working copy)
> > > > > @@ -2,10 +2,6 @@
> > > > >  package org.hibernate.lucene;
> > > > >
> > > > >  import java.io.Serializable;
> > > > > -import java.lang.reflect.AccessibleObject;
> > > > > -import java.lang.reflect.AnnotatedElement;
> > > > > -import java.lang.reflect.Member;
> > > > > -import java.lang.reflect.Method;
> > > > >  import java.lang.reflect.Modifier;  import
> > java.util.ArrayList; 
> > > > > import java.util.Collections; @@ -17,31 +13,46 @@  import
> > > > > org.apache.lucene.document.Document;
> > > > >  import org.apache.lucene.document.Field;  import
> > > > > org.apache.lucene.index.Term; -import
> > > > org.hibernate.AssertionFailure;
> > > > > import org.hibernate.HibernateException;
> > > > > +import org.hibernate.cfg.Configuration;
> > > > >  import org.hibernate.cfg.annotations.Version;
> > > > >  import org.hibernate.lucene.bridge.BridgeFactory;
> > > > >  import org.hibernate.lucene.bridge.FieldBridge;
> > > > >  import org.hibernate.lucene.event.LuceneEventListener;
> > > > >  import org.hibernate.lucene.store.DirectoryProvider;
> > > > > +import org.hibernate.lucene.store.DirectoryProviderFactory;
> > > > >  import org.hibernate.lucene.util.BinderHelper;
> > > > > +import org.hibernate.reflection.ReflectionManager;
> > > > > +import org.hibernate.reflection.XAnnotatedElement;
> > > > > +import org.hibernate.reflection.XClass; import
> > > > > +org.hibernate.reflection.XMember;
> > > > > +import org.hibernate.reflection.XMethod; import
> > > > > +org.hibernate.reflection.XProperty;
> > > > > +import org.hibernate.reflection.java.JavaXFactory;
> > > > >  import org.hibernate.util.ReflectHelper;
> > > > >
> > > > > -//TODO handle attribute (only getters are handled currently)
> > > > > +/**
> > > > > + * Set up and provide a manager for indexes classes
> > > > > + *
> > > > > + * @author Gavin King
> > > > > + * @author Emmanuel Bernard
> > > > > + * @author Sylvain Vieujot
> > > > > + */
> > > > >  public class DocumentBuilder<T> {
> > > > >
> > > > >      static {
> > > > >          Version.touch(); //touch version
> > > > >      }
> > > > >
> > > > > -    private final List<Member> keywordGetters = new
> > > > ArrayList<Member>();
> > > > > +    private final List<XMember> keywordGetters = new
> > > > > + ArrayList<XMember>();
> > > > >      private final List<String> keywordNames = new
> > > > ArrayList<String>();
> > > > > -    private final List<Member> unstoredGetters = new
> > > > ArrayList<Member>();
> > > > > +    private final List<XMember> unstoredGetters = new
> > > > > + ArrayList<XMember>();
> > > > >      private final List<String> unstoredNames = new
> > > > ArrayList<String>();
> > > > > -    private final List<Member> textGetters = new
> > > > ArrayList<Member>();
> > > > > +    private final List<XMember> textGetters = new
> > > > > + ArrayList<XMember>();
> > > > >      private final List<String> textNames = new
> > > > > ArrayList<String>();
> > > > >
> > > > >      private final Class<T> beanClass;
> > > > > +  
> > > > >      private final DirectoryProvider directoryProvider;
> > > > >      private String idKeywordName;
> > > > >      private final Analyzer analyzer; @@ -50,40 +61,30 @@
> > > > >      private FieldBridge idBridge;
> > > > >      private Set<Class> mappedSubclasses = new HashSet<Class>();
> > > > >
> > > > > -    public DocumentBuilder(Class<T> clazz, Analyzer
> > > > analyzer, DirectoryProvider directory) {
> > > > > -        this.beanClass = clazz;
> > > > > +   private ReflectionManager reflectionManager = new
> > > > JavaXFactory();
> > > > > +
> > > > > +   private DocumentBuilder(Class<T> clazz, Analyzer analyzer,
> > > > > +DirectoryProvider directory) {
> > > > > +
> > > > > +           XClass beanXClass =
> > reflectionManager.toXClass( clazz );
> > > > > +           if( !beanXClass.isAnnotationPresent(Indexed.class))
> > > > > +                   throw new IllegalArgumentException();
> > > > > +                 
> > > > > +           this.beanClass = clazz;
> > > > >          this.analyzer = analyzer;
> > > > >          this.directoryProvider = directory;
> > > > >
> > > > > -        for (Class currClass = clazz; currClass != null;
> > > > currClass = currClass.getSuperclass()) {
> > > > > -            Method[] methods = currClass.getDeclaredMethods();
> > > > > -            for (int i = 0; i < methods.length; i++) {
> > > > > -                Method method = methods[i];
> > > > > -                Keyword keywordAnn =
> > > > method.getAnnotation(Keyword.class);
> > > > > -                if (keywordAnn != null) {
> > > > > -                    String name =
> > > > BinderHelper.getAttributeName(method, keywordAnn.name());
> > > > > -                    if (keywordAnn.id()) {
> > > > > -                        idKeywordName = name;
> > > > > -                        idBoost = getBoost(method);
> > > > > -                        idBridge =
> > BridgeFactory.guessType(method);
> > > > > -                    } else {
> > > > > -                        setAccessible(method);
> > > > > -                        keywordGetters.add(method);
> > > > > -                        keywordNames.add(name);
> > > > > -                    }
> > > > > -                }
> > > > > -                Unstored unstoredAnn =
> > > > method.getAnnotation(Unstored.class);
> > > > > -                if (unstoredAnn != null) {
> > > > > -                    setAccessible(method);
> > > > > -                    unstoredGetters.add(method);
> > > > > -                  
> > > > unstoredNames.add(BinderHelper.getAttributeName(method,
> > > > unstoredAnn.name()));
> > > > > -                }
> > > > > -                Text textAnn =
> > method.getAnnotation(Text.class);
> > > > > -                if (textAnn != null) {
> > > > > -                    textGetters.add(method);
> > > > > -                  
> > > > textNames.add(BinderHelper.getAttributeName(method,
> > > > textAnn.name()));
> > > > > -                }
> > > > > -            }
> > > > > +
> > > > > +        for (XClass currClass = beanXClass; currClass !=
> > > > null; currClass = currClass.getSuperclass()) {
> > > > > +                   List<XMethod> methods =
> > > > currClass.getDeclaredMethods();
> > > > > +                   for ( XMethod method : methods ) {
> > > > > +                          
> > createMemberLuceneAttribute( method );
> > > > > +                   }
> > > > > +
> > > > > +                   // No filter applied = default filter (
> > > > no transient, no static fields)
> > > > > +                   List<XProperty> fields =
> > > > currClass.getDeclaredProperties("field");
> > > > > +                   for ( XProperty field : fields ) {
> > > > > +                          
> > createMemberLuceneAttribute( field );
> > > > > +                   }
> > > > >          }
> > > > >
> > > > >          if (idKeywordName == null) { @@ -91,42 +92,84 @@
> > > > >          }
> > > > >      }
> > > > >
> > > > > +   public static <S> DocumentBuilder<S>
> > > > createDocumentBuilder(Class<S> clazz, Analyzer analyzer,
> > > > DirectoryProviderFactory factory, Configuration cfg)
> > > > > +   {
> > > > > +           DocumentBuilder<S> result = null;
> > > > > +         
> > > > > +           try {
> > > > > +                   result = new DocumentBuilder<S>(clazz,
> > > > analyzer, factory.createDirectoryProvider(clazz, cfg));
> > > > > +           }
> > > > > +           catch (Exception e) {
> > > > > +           }
> > > > > +         
> > > > > +           return result;
> > > > > +   }                     
> > > > >
> > > > > -    private Float getBoost(AnnotatedElement element) {
> > > > > +   private void createMemberLuceneAttribute(XMember member) {
> > > > > +           Keyword keywordAnn =
> > > > member.getAnnotation(Keyword.class);
> > > > > +        if (keywordAnn != null) {
> > > > > +            String name =
> > > > BinderHelper.getAttributeName(member, keywordAnn.name());
> > > > > +            if (keywordAnn.id()) {
> > > > > +                idKeywordName = name;
> > > > > +                idBoost = getBoost(member);
> > > > > +                idBridge = BridgeFactory.guessType(member);
> > > > > +            } else {
> > > > > +                setAccessible(member);
> > > > > +                keywordGetters.add(member);
> > > > > +                keywordNames.add(name);
> > > > > +            }
> > > > > +        }
> > > > > +
> > > > > +        Unstored unstoredAnn =
> > > > member.getAnnotation(Unstored.class);
> > > > > +        if (unstoredAnn != null) {
> > > > > +            setAccessible(member);
> > > > > +            unstoredGetters.add(member);
> > > > > +          
> > > > unstoredNames.add(BinderHelper.getAttributeName(member,
> > > > unstoredAnn.name()));
> > > > > +        }
> > > > > +
> > > > > +        Text textAnn = member.getAnnotation(Text.class);
> > > > > +        if (textAnn != null) {
> > > > > +            setAccessible(member);
> > > > > +            textGetters.add(member);
> > > > > +          
> > > > textNames.add(BinderHelper.getAttributeName(member,
> > > > textAnn.name()));
> > > > > +        }
> > > > > +   }
> > > > > +
> > > > > +   private Float getBoost(XAnnotatedElement element) {
> > > > >          if (element == null) return null;
> > > > >          Boost boost = element.getAnnotation(Boost.class);
> > > > >          return boost != null ?
> > Float.valueOf(boost.value()) : null;
> > > > >      }
> > > > >
> > > > > -    private Object getValue(Member member, T bean) {
> > > > > -        try {
> > > > > -            if (member instanceof java.lang.reflect.Field) {
> > > > > -                return ((java.lang.reflect.Field)
> > > > member).get(bean);
> > > > > -            } else if (member instanceof Method) {
> > > > > -                return ((Method) member).invoke(bean);
> > > > > -            } else {
> > > > > -                throw new AssertionFailure("Unexpected
> > > > member: " + member.getClass().getName());
> > > > > -            }
> > > > > -        }
> > > > > -        catch (Exception e) {
> > > > > -            throw new IllegalStateException("Could not get
> > > > property value", e);
> > > > > -        }
> > > > > +    private Object getMemberValue(XMember member, T bean) {
> > > > > +           Object value;
> > > > > +           try {
> > > > > +                   value = member.invoke( bean );
> > > > > +           }
> > > > > +           catch (Exception e) {
> > > > > +                   throw new IllegalStateException( "Could
> > > > not get property value", e );
> > > > > +           }
> > > > > +           return value;
> > > > >      }
> > > > >
> > > > >      public Document getDocument(T instance, Serializable id) {
> > > > > +           XClass xClazz =
> > > > reflectionManager.toXClass(instance.getClass());
> > > > > +         
> > > > >          Document doc = new Document();
> > > > > -        Float boost = getBoost(instance.getClass());
> > > > > +        Float boost = getBoost(xClazz);
> > > > >          if (boost != null) {
> > > > >              doc.setBoost(boost.floatValue());
> > > > >          }
> > > > > +
> > > > >          {
> > > > > -            Field classField = new Field(CLASS_FIELDNAME,
> > > > instance.getClass().getName(), Field.Store.YES, Field.Index.NO);
> > > > > +            Field classField = new Field(CLASS_FIELDNAME,
> > > > > + xClazz.getName(), Field.Store.YES, Field.Index.NO);
> > > > >              doc.add(classField);
> > > > >              idBridge.set(idKeywordName, id, doc,
> > > > Field.Store.YES, Field.Index.UN_TOKENIZED, idBoost);
> > > > >          }
> > > > > +      
> > > > >          for (int i = 0; i < keywordNames.size(); i++) {
> > > > > -            Member member = keywordGetters.get(i);
> > > > > -            Object value = getValue(member, instance);
> > > > > +            XMember member = keywordGetters.get(i);
> > > > > +            Object value = getMemberValue(member, instance);
> > > > >              if (value != null) {
> > > > >                  Field field = new
> > > > Field(keywordNames.get(i), toString(value), Field.Store.YES,
> > > > Field.Index.UN_TOKENIZED);
> > > > >                  boostField(field, member); @@ -134,8 +177,8 @@
> > > > >              }
> > > > >          }
> > > > >          for (int i = 0; i < textNames.size(); i++) {
> > > > > -            Member member = textGetters.get(i);
> > > > > -            Object value = getValue(member, instance);
> > > > > +            XMember member = textGetters.get(i);
> > > > > +            Object value = getMemberValue(member, instance);
> > > > >              if (value != null) {
> > > > >                  Field field = new Field(textNames.get(i),
> > > > toString(value), Field.Store.YES, Field.Index.TOKENIZED);
> > > > >                  boostField(field, member); @@ -143,8 +186,8 @@
> > > > >              }
> > > > >          }
> > > > >          for (int i = 0; i < unstoredNames.size(); i++) {
> > > > > -            Member member = unstoredGetters.get(i);
> > > > > -            Object value = getValue(member, instance);
> > > > > +            XMember member = unstoredGetters.get(i);
> > > > > +            Object value = getMemberValue(member, instance);
> > > > >              if (value != null) {
> > > > >                  Field field = new
> > > > Field(unstoredNames.get(i), toString(value), Field.Store.NO,
> > > > Field.Index.TOKENIZED);
> > > > >                  boostField(field, member); @@ -155,8 +198,8 @@
> > > > >          return doc;
> > > > >      }
> > > > >
> > > > > -    private void boostField(Field field, Member member) {
> > > > > -        Float boost = getBoost((AnnotatedElement) member);
> > > > > +    private void boostField(Field field, XMember member) {
> > > > > +        Float boost = getBoost(member);
> > > > >          if (boost != null) field.setBoost(boost.floatValue());
> > > > >      }
> > > > >
> > > > > @@ -176,11 +219,11 @@
> > > > >          return analyzer;
> > > > >      }
> > > > >
> > > > > -    private static void setAccessible(Member member) {
> > > > > -        if (!Modifier.isPublic(member.getModifiers())) {
> > > > > -            ((AccessibleObject) member).setAccessible(true);
> > > > > -        }
> > > > > -    }
> > > > > +   private static void setAccessible(XMember member) {
> > > > > +           if ( !Modifier.isPublic( member.getModifiers() ) ) {
> > > > > +                   member.setAccessible( true );
> > > > > +           }
> > > > > +   }
> > > > >
> > > > >      public FieldBridge getIdBridge() {
> > > > >          return idBridge;
> > > > > Index:
> > > > >
> > > >
> > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/hibern
> > > > at
> > > > > e/lucene/Boost.java
> > > > >
> > ==================================================================
> > > > > =
> > > > > ---
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/
> > > > hibernate/lucene/Boost.java   (revision 10571)
> > > > > +++
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/
> > > > hibernate/lucene/Boost.java   (working copy)
> > > > > @@ -13,7 +13,7 @@
> > > > >   * @author Emmanuel Bernard
> > > > >   */
> > > > >  @Retention(RetentionPolicy.RUNTIME)
> > > > > - at Target({ElementType.TYPE, ElementType.METHOD})
> > > > > + at Target({ElementType.TYPE, ElementType.METHOD,
> > > > > +ElementType.FIELD})
> > > > >  @Documented
> > > > >  public @interface Boost {
> > > > >     float value();
> > > > > Index:
> > > > >
> > > >
> > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/hibern
> > > > at
> > > > > e/lucene/bridge/BridgeFactory.java
> > > > >
> > ==================================================================
> > > > > =
> > > > > ---
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/
> > > > hibernate/lucene/bridge/BridgeFactory.java    (revision 10571)
> > > > > +++
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/
> > > > hibernate/lucene/bridge/BridgeFactory.java    (working copy)
> > > > > @@ -1,8 +1,6 @@
> > > > >  //$Id: $
> > > > >  package org.hibernate.lucene.bridge;
> > > > >
> > > > > -import java.lang.reflect.AnnotatedElement;
> > > > > -import java.lang.reflect.Member;
> > > > >  import java.util.Date;
> > > > >  import java.util.HashMap;
> > > > >  import java.util.Map;
> > > > > @@ -10,6 +8,8 @@
> > > > >  import org.hibernate.HibernateException;  import
> > > > > org.hibernate.annotations.Parameter;
> > > > >  import org.hibernate.lucene.util.BinderHelper;
> > > > > +import org.hibernate.reflection.XClass; import
> > > > > +org.hibernate.reflection.XMember;
> > > > >
> > > > >  /**
> > > > >   * @author Emmanuel Bernard
> > > > > @@ -62,9 +62,9 @@
> > > > >             return new String2FieldBridgeAdaptor( date );
> > > > >     }
> > > > >
> > > > > -   public static FieldBridge guessType(Member member) {
> > > > > +   public static FieldBridge guessType(XMember member) {
> > > > >             FieldBridge bridge = null;
> > > > > -           org.hibernate.lucene.FieldBridge bridgeAnn = (
> > > > (AnnotatedElement) member ).getAnnotation(
> > > > org.hibernate.lucene.FieldBridge.class );
> > > > > +           org.hibernate.lucene.FieldBridge bridgeAnn =
> > > > member.getAnnotation(
> > > > > +org.hibernate.lucene.FieldBridge.class );
> > > > >             if (bridgeAnn != null) {
> > > > >                     Class impl = bridgeAnn.impl();
> > > > >                     try {
> > > > > @@ -89,7 +89,7 @@
> > > > >             }
> > > > >             else {
> > > > >                     //find in built-ins
> > > > > -                   Class<?> returnType =
> > > > BinderHelper.getReturnType( member );
> > > > > +                   XClass returnType =
> > > > BinderHelper.getReturnType( member );
> > > > >                     bridge = builtInBridges.get(
> > > > returnType.getName() );
> > > > >             }
> > > > >             if (bridge == null) throw new
> > > > HibernateException("Unable to guess
> > > > > FieldBridge for " + BinderHelper.getAttributeName(member) );
> > > > > Index:
> > > > >
> > > >
> > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/hibern
> > > > at
> > > > > e/lucene/util/BinderHelper.java
> > > > >
> > ==================================================================
> > > > > =
> > > > > ---
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/
> > > > hibernate/lucene/util/BinderHelper.java       (revision 10571)
> > > > > +++
> > > > C:/dev/source/workspace1.5/HibernateExt/metadata/src/java/org/
> > > > hibernate/lucene/util/BinderHelper.java       (working copy)
> > > > > @@ -1,11 +1,13 @@
> > > > >  //$Id: $
> > > > >  package org.hibernate.lucene.util;
> > > > >
> > > > > -import java.lang.reflect.Method;
> > > > > -import java.lang.reflect.Member;
> > > > > -import java.lang.reflect.Field;
> > > > >  import java.beans.Introspector;
> > > > >
> > > > > +import org.hibernate.AssertionFailure; import
> > > > > +org.hibernate.reflection.XClass; import
> > > > > +org.hibernate.reflection.XMember;
> > > > > +import org.hibernate.reflection.XMethod; import
> > > > > +org.hibernate.reflection.XProperty;
> > > > >  import org.hibernate.util.StringHelper;
> > > > >
> > > > >  /**
> > > > > @@ -15,37 +17,41 @@
> > > > >
> > > > >     private BinderHelper() {}
> > > > >
> > > > > -   public static String getAttributeName(Member member) {
> > > > > +   public static String getAttributeName(XMember member) {
> > > > >             return getAttributeName( member, null );
> > > > >     }
> > > > >     /**
> > > > >      * Get attribute name out of member unless overriden by
> > > > <code>name</code>
> > > > >      */
> > > > > -   //TODO move to reflection layer
> > > > > -   public static String getAttributeName(Member member,
> > > > String name) {
> > > > > -           if( StringHelper.isNotEmpty( name ) ) return
> > > > name; //explicit field name
> > > > > -           if (member instanceof Field ) {
> > > > > -                   return ( (Field) member ).getName();
> > > > > +   public static String getAttributeName(XMember member,
> > > > String name) {
> > > > > +           return StringHelper.isNotEmpty( name ) ? name :
> > > > getPropertyName(member);
> > > > > +   }
> > > > > +
> > > > > +   public static XClass getReturnType(XMember member) {
> > > > > +           return member.getType();
> > > > > +   }
> > > > > + 
> > > > > +    // Could be located in a better place, shared method
> > > > with ClassValidator
> > > > > +   public static String getPropertyName(XMember member) {
> > > > > +           //Do no try to cache the result in a map, it's
> > > > actually much slower (2.x time)
> > > > > +           String propertyName;
> > > > > +           if ( XProperty.class.isAssignableFrom(
> > > > member.getClass() ) ) {
> > > > > +                   propertyName = member.getName();
> > > > >             }
> > > > > -           else {
> > > > > -                   //decapitalize
> > > > > -                   String methodName = ( (Method)
> > > > member).getName();
> > > > > -                   //FIXME we probably should exclude
> > > > methods not starting with "get" nor "is"
> > > > > -                   int startIndex = 3;
> > > > > -                   if( methodName.startsWith("is") ) {
> > > > > -                           startIndex = 2;
> > > > > +           else if ( XMethod.class.isAssignableFrom(
> > > > member.getClass() ) ) {
> > > > > +                   propertyName = member.getName();
> > > > > +                   if ( propertyName.startsWith( "is" ) ) {
> > > > > +                           propertyName =
> > > > Introspector.decapitalize( propertyName.substring(
> > > > > +2 ) );
> > > > >                     }
> > > > > -                   return Introspector.decapitalize(
> > > > methodName.substring( startIndex ) );
> > > > > +                   else if ( propertyName.startsWith(
> > "get" ) ) {
> > > > > +                           propertyName =
> > > > Introspector.decapitalize( propertyName.substring( 3 ) );
> > > > > +                   }
> > > > > +                   //do nothing for non getter method, in
> > > > case someone want to
> > > > > +validate a PO Method
> > > > >             }
> > > > > -   }
> > > > > -
> > > > > -   //TODO move to reflection layer
> > > > > -   public static Class<?> getReturnType(Member member) {
> > > > > -           if (member instanceof Field) {
> > > > > -                   return ( (Field) member ).getType();
> > > > > -           }
> > > > >             else {
> > > > > -                   return ( (Method) member ).getReturnType();
> > > > > +                   throw new AssertionFailure( "Unexpected
> > > > member: " +
> > > > > +member.getClass().getName() );
> > > > >             }
> > > > > +           return propertyName;
> > > > >     }
> > > > > +
> > > > >  }
> > > > > 
> > > >
> > >
> >
>



More information about the hibernate-dev mailing list