[hibernate-commits] Hibernate SVN: r10867 - in branches/Branch_3_2/HibernateExt/metadata: doc/reference/en/modules lib src/java/org/hibernate src/java/org/hibernate/search src/java/org/hibernate/search/annotations src/java/org/hibernate/search/backend src/java/org/hibernate/search/backend/impl src/java/org/hibernate/search/bridge src/java/org/hibernate/search/bridge/builtin src/java/org/hibernate/search/engine src/java/org/hibernate/search/event src/java/org/hibernate/search/impl src/java/org/hibernate/search/query src/java/org/hibernate/search/store src/java/org/hibernate/search/util src/test/org/hibernate src/test/org/hibernate/search src/test/org/hibernate/search/test src/test/org/hibernate/search/test/bridge src/test/org/hibernate/search/test/fieldAccess src/test/org/hibernate/search/test/inheritance src/test/org/hibernate/search/test/query src/test/org/hibernate/search/test/session

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Thu Nov 23 19:38:22 EST 2006


Author: epbernard
Date: 2006-11-23 19:37:46 -0500 (Thu, 23 Nov 2006)
New Revision: 10867

Added:
   branches/Branch_3_2/HibernateExt/metadata/lib/jta.jar
   branches/Branch_3_2/HibernateExt/metadata/lib/jta.licence.txt
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/Environment.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/FullTextSession.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/Search.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Boost.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/DateBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/DocumentId.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Field.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/FieldBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Index.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Indexed.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Keyword.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Resolution.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Store.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Text.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Unstored.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/AddWork.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/DeleteWork.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/UpdateWork.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/Work.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/WorkQueue.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/Workspace.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/BatchLuceneWorkQueue.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/LuceneWorker.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/PostTransactionWorkQueueSynchronization.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/BridgeFactory.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/FieldBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/ParameterizedBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/String2FieldBridgeAdaptor.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/StringBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/TwoWayFieldBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/TwoWayString2FieldBridgeAdaptor.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/TwoWayStringBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/BigDecimalBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/BigIntegerBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/DateBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/DoubleBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/FloatBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/IntegerBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/LongBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/NumberBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/ShortBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/StringBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/engine/
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/engine/DocumentBuilder.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/event/
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/event/FullTextIndexEventListener.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/impl/
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/impl/FullTextSessionImpl.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/EntityInfo.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/FullTextQueryImpl.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/IteratorImpl.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/ScrollableResultsImpl.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/DirectoryProvider.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/DirectoryProviderFactory.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/FSDirectoryProvider.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/RAMDirectoryProvider.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/util/
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/util/BinderHelper.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/util/ContextHelper.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/AlternateDocument.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/Clock.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/Document.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/FSDirectoryTest.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/RamDirectoryTest.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/TestCase.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/TransactionTest.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/BridgeTest.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/Cloud.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/DateSplitBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/PaddedIntegerBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/TruncateFieldBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/TruncateStringBridge.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/fieldAccess/
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/fieldAccess/Document.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/fieldAccess/FieldAccessTest.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/inheritance/
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/inheritance/Animal.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/inheritance/InheritanceTest.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/inheritance/Mammal.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/AlternateBook.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/Book.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/Clock.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/LuceneQueryTest.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/session/
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/session/Email.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/session/MassIndexTest.java
Removed:
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/lucene/
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/lucene/
Modified:
   branches/Branch_3_2/HibernateExt/metadata/doc/reference/en/modules/lucene.xml
Log:
Merge back Lucene_integration to 3.2

Modified: branches/Branch_3_2/HibernateExt/metadata/doc/reference/en/modules/lucene.xml
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/doc/reference/en/modules/lucene.xml	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/doc/reference/en/modules/lucene.xml	2006-11-24 00:37:46 UTC (rev 10867)
@@ -1,91 +1,63 @@
 <?xml version="1.0" encoding="ISO-8859-1"?>
-<chapter id="lucene" revision="1">
-  <title>Hibernate Lucene Integration</title>
+<chapter id="lucene" revision="2">
+  <title>Hibernate Search: Apache <trademark>Lucene</trademark>
+  Integration</title>
 
-  <para>Lucene is a high-performance Java search engine library available from
-  the Apache Software Foundation. Hibernate Annotations includes a package of
-  annotations that allows you to mark any domain model object as indexable and
-  have Hibernate maintain a Lucene index of any instances persisted via
-  Hibernate.</para>
+  <para><ulink url="http://lucene.apache.org">Apache Lucene</ulink> is a
+  high-performance Java search engine library available at the Apache Software
+  Foundation. Hibernate Annotations includes a package of annotations that
+  allows you to mark any domain model object as indexable and have Hibernate
+  maintain a Lucene index of any instances persisted via Hibernate. Apache
+  Lucene is also integrated with the Hibernate query facility.</para>
 
-  <para>Hibernate Lucene is a work in progress and new features are cooking in
+  <para>Hibernate Search is a work in progress and new features are cooking in
   this area. So expect some compatibility changes in subsequent
   versions.</para>
 
-  <section id="lucene-mapping">
-    <title>Mapping the entities to the index</title>
+  <section id="lucene-architecture">
+    <title>Architecture</title>
 
-    <para>First, we must declare a persistent class as indexable. This is done
-    by annotating the class with <literal>@Indexed</literal>:</para>
+    <para>Hibernate Search is made of an indexing engine and an index search
+    engine. Both are backed by Apache Lucene.</para>
 
-    <programlisting>@Entity
- at Indexed(index="indexes/essays")
-public class Essay {
-    ...
-}</programlisting>
+    <para>When an entity is inserted, updated or removed to/from the database,
+    <productname>Hibernate Search</productname> will keep track of this event
+    (through the Hibernate event system) and schedule an index update. When
+    out of transaction, the update is executed right after the actual database
+    operation. It is however recommended, for both your database and Hibernate
+    Search, to execute your operation in a transaction (whether JDBC or JTA).
+    When in a transaction, the index update is schedule for the transaction
+    commit (and discarded in case of transaction rollback). You can think of
+    this as the regular (infamous) autocommit vs transactional behavior. From
+    a performance perspective, the <emphasis>in transaction</emphasis> mode is
+    recommended. All the index updates are handled for you without you having
+    to use the Apache Lucene APIs.</para>
 
-    <para>The <literal>index</literal> attribute tells Hibernate what the
-    lucene directory name is (usually a directory on your file system). If you
-    wish to define a base directory for all lucene indexes, you can use the
-    <literal>hibernate.lucene.default.indexDir</literal> property in your
-    configuration file.</para>
+    <para>To interact with Apache Lucene indexes, Hibernate Search has the
+    notion of <classname>DirectoryProvider</classname>. A directory provider
+    will manage a given Lucene <classname>Directory</classname> type. You can
+    configure directory providers to adjust the directory target.</para>
 
-    <para>Lucene indexes contain four kinds of fields:
-    <emphasis>keyword</emphasis> fields, <emphasis>text</emphasis> fields,
-    <emphasis>unstored</emphasis> fields and <emphasis>unindexed</emphasis>
-    fields. Hibernate Annotations provides annotations to mark a property of
-    an entity as one of the first three kinds of indexed fields.</para>
-
-    <programlisting>@Entity
- at Indexed(index="indexes/essays")
-public class Essay {
-    ...
-
-    @Id
-    @Keyword(id=true)
-    public Long getId() { return id; }
-    
-    @Text(name="Abstract")
-    public String getSummary() { return summary; }
-    
-    @Lob
-    @Unstored
-    public String getText() { return text; }
-    
-}</programlisting>
-
-    <para>These annotations define an index with three fields:
-    <literal>id</literal>, <literal>Abstract</literal> and
-    <literal>text</literal>. Note that by default the field name is
-    decapitalized, following the JavaBean specification.</para>
-
-    <para>Note: you <emphasis>must</emphasis> specify
-    <literal>@Keyword(id=true)</literal> on the identifier property of your
-    entity class.</para>
-
-    <para>Lucene has the notion of <emphasis>boost factor</emphasis>. It's a
-    way to give more weigth to a field or to an indexed element over an other
-    during the indexation process. You can use <literal>@Boost</literal> at
-    the field or the class level.</para>
-
-    <para>The analyzer class used to index the elements is configurable
-    through the <literal>hibernate.lucene.analyzer</literal> property. If none
-    defined,
-    <classname>org.apache.lucene.analysis.standard.StandardAnalyzer</classname>
-    is used as the default.</para>
+    <para><productname>Hibernate Search</productname> can also use a Lucene
+    index to search an entity and return a (list of) managed entity saving you
+    from the tedious Object / Lucene Document mapping and low level Lucene
+    APIs. The application code use the unified
+    <classname>org.hibernate.Query</classname> API exactly the way a HQL or
+    native query would be done.</para>
   </section>
 
   <section id="lucene-configuration">
     <title>Configuration</title>
 
     <section id="lucene-configuration-directory">
-      <title>directory configuration</title>
+      <title>Directory configuration</title>
 
-      <para>Lucene has a notion of Directory where the index is stored. The
-      Directory implementation can be customized but Lucene comes bundled with
-      a file system and a full memory implementation. Hibernate Lucene has the
-      notion of <literal>DirectoryProvider</literal> that handle the
-      configuration and the initialization of the Lucene Directory.</para>
+      <para>Apache Lucene has a notion of Directory where the index is stored.
+      The Directory implementation can be customized but Lucene comes bundled
+      with a file system and a full memory implementation.
+      <productname>Hibernate Search</productname> has the notion of
+      <literal>DirectoryProvider</literal> that handle the configuration and
+      the initialization of the Lucene Directory.</para>
 
       <table>
         <title>List of built-in Directory Providers</title>
@@ -103,19 +75,19 @@
 
           <tbody>
             <row>
-              <entry>org.hibernate.lucene.store.FSDirectoryProvider</entry>
+              <entry>org.hibernate.search.store.FSDirectoryProvider</entry>
 
               <entry>File system based directory. The directory used will be
-              &lt;indexBase&gt;/&lt;<literal>@Index.name</literal>&gt;</entry>
+              &lt;indexBase&gt;/&lt;<literal>@Indexed.name</literal>&gt;</entry>
 
               <entry><literal>indexBase</literal>: Base directory</entry>
             </row>
 
             <row>
-              <entry>org.hibernate.lucene.store.RAMDirectoryProvider</entry>
+              <entry>org.hibernate.search.store.RAMDirectoryProvider</entry>
 
               <entry>Memory based directory, the directory will be uniquely
-              indentified by the <literal>@Index.name</literal>
+              indentified by the <literal>@Indexed.name</literal>
               element</entry>
 
               <entry>none</entry>
@@ -132,17 +104,17 @@
       <para>Each indexed entity is associated to a Lucene index (an index can
       be shared by several entities but this is not usually the case). You can
       configure the index through properties prefixed by
-      <literal><literal>hibernate.lucene.&lt;indexname&gt;</literal></literal>.
+      <constant>hibernate.search.</constant><replaceable>indexname</replaceable>.
       Default properties inherited to all indexes can be defined using the
-      prefix hibernate.lucene.default.</para>
+      prefix <constant>hibernate.search.default.</constant></para>
 
       <para>To define the directory provider of a given index, you use the
-      <literal>hibernate.lucene.&lt;indexname&gt;.directory_provider</literal></para>
+      <constant>hibernate.search.<replaceable>indexname</replaceable>.directory_provider</constant></para>
 
-      <programlisting>hibernate.lucene.default.directory_provider org.hibernate.lucene.store.FSDirectoryProvider
-hibernate.lucene.default.indexDir=/usr/lucene/indexes
+      <programlisting>hibernate.search.default.directory_provider org.hibernate.search.store.FSDirectoryProvider
+hibernate.search.default.indexDir=/usr/lucene/indexes
 
-hibernate.lucene.Rules.directory_provider org.hibernate.lucene.store.RAMDirectoryProvider
+hibernate.search.Rules.directory_provider org.hibernate.search.store.RAMDirectoryProvider
 </programlisting>
 
       <para>applied on</para>
@@ -162,32 +134,537 @@
       and base directory, and overide those default later on on a per index
       basis.</para>
 
-      <para>Writing your own DirectoryProvider, you can benefit this
-      configuration mechanism too.</para>
+      <para>Writing your own <classname>DirectoryProvider</classname>, you can
+      benefit this configuration mechanism too.</para>
     </section>
 
-    <section id="lucene-configuration-event">
+    <section id="lucene-configuration-event" revision="1">
       <title>Enabling automatic indexing</title>
 
-      <para>Finally, we enable the <literal>LuceneEventListener</literal> for
-      the three Hibernate events that occur after changes are committed to the
+      <para>Finally, we enable the <literal>SearchEventListener</literal> for
+      the three Hibernate events that occur after changes are executed to the
       database.</para>
 
       <programlisting>&lt;hibernate-configuration&gt;
     ...
-    &lt;event type="post-commit-update" 
-        &lt;listener  
-            class="org.hibernate.lucene.event.LuceneEventListener"/&gt;
+    &lt;event type="post-update" 
+        &lt;listener class="org.hibernate.search.event.FullTextIndexEventListener"/&gt;
     &lt;/event&gt;
-    &lt;event type="post-commit-insert" 
-        &lt;listener 
-            class="org.hibernate.lucene.event.LuceneEventListener"/&gt;
+    &lt;event type="post-insert" 
+        &lt;listener class="org.hibernate.search.event.FullTextIndexEventListener"/&gt;
     &lt;/event&gt;
-    &lt;event type="post-commit-delete" 
-        &lt;listener 
-            class="org.hibernate.lucene.event.LuceneEventListener"/&gt;
+    &lt;event type="post-delete" 
+        &lt;listener class="org.hibernate.search.event.FullTextIndexEventListener"/&gt;
     &lt;/event&gt;
 &lt;/hibernate-configuration&gt;</programlisting>
     </section>
   </section>
+
+  <section id="lucene-mapping" revision="1">
+    <title>Mapping entities to the index structure</title>
+
+    <para>All the metadata information related to indexed entities is
+    described through some Java annotations. There is no need for xml mapping
+    files nor a list of indexed entities. The list is discovered at startup
+    time scanning the Hibernate mapped entities.</para>
+
+    <para>First, we must declare a persistent class as indexable. This is done
+    by annotating the class with <literal>@Indexed</literal> (all entities not
+    annotated with <literal>@Indexed</literal> will be ignored by the indexing
+    process):</para>
+
+    <programlisting>@Entity
+<emphasis role="bold">@Indexed(index="indexes/essays")</emphasis>
+public class Essay {
+    ...
+}</programlisting>
+
+    <para>The <literal>index</literal> attribute tells Hibernate what the
+    Lucene directory name is (usually a directory on your file system). If you
+    wish to define a base directory for all Lucene indexes, you can use the
+    <literal>hibernate.search.default.indexDir</literal> property in your
+    configuration file. Each entity instance will be represented by a Lucene
+    <classname>Document</classname> inside the given index (aka
+    Directory).</para>
+
+    <para>For each property (or attribute) of your entity, you have the
+    ability to describe how it will be indexed. The default (ie no annotation)
+    means that the property is completly ignored by the indexing process.
+    <literal>@Field</literal> does declare a property as indexed. When
+    indexing an element to a Lucene document you can specify how it is
+    indexed:</para>
+
+    <itemizedlist>
+      <listitem>
+        <para><literal>name</literal>: describe under which name, the property
+        should be stored in the Lucene Document. The default value is the
+        property name (following the JavaBeans convention)</para>
+      </listitem>
+
+      <listitem>
+        <para><literal>store</literal>: describe whether or not the property
+        is stored in the Lucene index. You can store the value
+        <literal>Store.YES</literal> (comsuming more space in the index),
+        store it in a compressed way <literal>Store.COMPRESS</literal> (this
+        does consume more CPU), or avoid any storage
+        <literal>Store.NO</literal> (this is the default value). When a
+        property is stored, you can retrieve it from the Lucene Document (note
+        that this is not related to whether the element is indexed or
+        not).</para>
+      </listitem>
+
+      <listitem>
+        <para>index: describe how the element is indexed (ie the process used
+        to index the property and the type of information store). The
+        different values are <literal>Index.NO</literal> (no indexing, ie
+        cannot be found by a query), <literal>Index.TOKENIZED</literal> (use
+        an analyzer to process the property),
+        <literal>Index.UN_TOKENISED</literal> (no analyzer pre processing),
+        <literal>Index.NO_NORM</literal> (do not store the normalization
+        data).</para>
+      </listitem>
+    </itemizedlist>
+
+    <para>These attributes are part of the <literal>@Field</literal>
+    annotation.</para>
+
+    <para>Whether or not you want to store the data depends on how you wish to
+    use the index query result. As of today, for a pure <productname>Hibernate
+    Search</productname> usage, storing is not necessary. Whether or not you
+    want to tokenize a property or not depends on whether you wish to search
+    the element as is, or only normalized part of it. It make sense to
+    tokenize a text field, but it does not to do it for a date field (or an id
+    field).</para>
+
+    <para>Finally, the id property of an entity is a special property used by
+    <productname>Hibernate Search</productname> to ensure index unicity of a
+    given entity. By design, an id has to be stored and must not be tokenized.
+    To mark a property as index id, use the <literal>@DocumentId</literal>
+    annotation.</para>
+
+    <programlisting>@Entity
+ at Indexed(index="indexes/essays")
+public class Essay {
+    ...
+
+    @Id
+    <emphasis role="bold">@DocumentId</emphasis>
+    public Long getId() { return id; }
+    
+    <emphasis role="bold">@Field(name="Abstract", index=Index.TOKENIZED, store=Store.YES)</emphasis>
+    public String getSummary() { return summary; }
+    
+    @Lob
+    <emphasis role="bold">@Field(index=Index.TOKENIZED)</emphasis>
+    public String getText() { return text; }
+    
+}</programlisting>
+
+    <para>These annotations define an index with three fields:
+    <literal>id</literal>, <literal>Abstract</literal> and
+    <literal>text</literal>. Note that by default the field name is
+    decapitalized, following the JavaBean specification.</para>
+
+    <para>Note: you <emphasis>must</emphasis> specify
+    <literal>@DocumentId</literal> on the identifier property of your entity
+    class.</para>
+
+    <para>Lucene has the notion of <emphasis>boost factor</emphasis>. It's a
+    way to give more weigth to a field or to an indexed element over an other
+    during the indexation process. You can use <literal>@Boost</literal> at
+    the field or the class level.</para>
+
+    <programlisting>@Entity
+ at Indexed(index="indexes/essays")
+<emphasis role="bold">@Boost(2)</emphasis>
+public class Essay {
+    ...
+
+    @Id
+    @DocumentId
+    public Long getId() { return id; }
+    
+    @Field(name="Abstract", index=Index.TOKENIZED, store=Store.YES)
+    <emphasis role="bold">@Boost(2.5f)</emphasis>
+    public String getSummary() { return summary; }
+    
+    @Lob
+    @Field(index=Index.TOKENIZED)
+    public String getText() { return text; }
+    
+}</programlisting>
+
+    <para>In our example, Essay's probability to reach the top of the search
+    list will be multiplied by 2 and the summary field will be 2.5 more
+    important than the test field. Note that this explaination is actually
+    wrong, but it is simple and close enought to the reality. Please check the
+    Lucene documentation or the excellent <citetitle>Lucene In
+    Action</citetitle> from Otis Gospodnetic and Erik Hatcher.</para>
+
+    <para>The analyzer class used to index the elements is configurable
+    through the <literal>hibernate.search.analyzer</literal> property. If none
+    defined,
+    <classname>org.apache.lucene.analysis.standard.StandardAnalyzer</classname>
+    is used as the default.</para>
+  </section>
+
+  <section id="lucene-bridge">
+    <title>Property/Field Bridge</title>
+
+    <para>All field of a full text index in Lucene have to be represented as
+    Strings. Ones Java properties have to be indexed in a String form. For
+    most of your properties, <productname>Hibernate Search</productname> does
+    the translation job for you thanks to a built-in set of bridges. In some
+    cases, though you need a fine grain control over the translation
+    process.</para>
+
+    <section>
+      <title>Built-in bridges</title>
+
+      <para><literal>Hibernate Search</literal> comes bundled with a set of
+      built-in bridges between a Java property type and its full text
+      representation.</para>
+
+      <para><literal>Null</literal> elements are not indexed (Lucene does not
+      support null elements and it does not make much sense either)</para>
+
+      <variablelist>
+        <varlistentry>
+          <term>null</term>
+
+          <listitem>
+            <para>null elements are not indexed. Lucene does not support null
+            elements and this does not make much sense either.</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>java.lang.String</term>
+
+          <listitem>
+            <para>String are indexed as is</para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>short, Short, integer, Integer, long, Long, float, Float,
+          double, Double, BigInteger, BigDecimal</term>
+
+          <listitem>
+            <para>Numbers are converted in their String representation. Note
+            that numbers cannot be compared by Lucene (ie used in ranged
+            queries) out of the box: they have to be padded <footnote>
+                <para>Using a Range query is debattable and has drawbacks, an
+                alternative approach is to use a Filter query which will
+                filter the result query to the appropriate range.</para>
+
+                <para><productname>Hibernate Search</productname> will support
+                a padding mechanism</para>
+              </footnote></para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>java.util.Date</term>
+
+          <listitem>
+            <para>Dates are stored as yyyyMMddHHmmssSSS in GMT time
+            (200611072203012 for Nov 7th of 2006 4:03PM and 12ms EST). You
+            shouldn't really bother with the internal format. What is
+            important is that when using a DateRange Query, you should know
+            that the dates have to be expressed in GMT time.</para>
+
+            <para>Usually, storing the date up to the milisecond is not
+            necessary. <literal>@DateBridge</literal> defines the appropriate
+            resolution you are willing to store in the index
+            (<literal><literal>@DateBridge(resolution=Resolution.DAY)</literal></literal>).
+            The date pattern will then be truncated accordingly.</para>
+
+            <programlisting>@Entity @Indexed 
+public class Meeting {
+    @Field(index=Index.UN_TOKENIZED)
+    <emphasis role="bold">@DateBridge(resolution=Resolution.MINUTE)</emphasis>
+    private Date date;
+    ...
+}</programlisting>
+
+            <warning>
+              <para>A Date whose resolution is lower than
+              <literal>MILLISECOND</literal> cannot be a
+              <literal>@DocumentId</literal></para>
+            </warning>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+
+      <para></para>
+    </section>
+
+    <section>
+      <title>Custom Bridge</title>
+
+      <para>It can happen that the built-in bridges of Hibernate Search does
+      not cover some of your property types, or that the String representation
+      used is not what you expect.</para>
+
+      <section>
+        <title>StringBridge</title>
+
+        <para>The simpliest custom solution is to give <productname>Hibernate
+        Search</productname> an implementation of your expected
+        <emphasis>object to String</emphasis> bridge. To do so you need to
+        implements the
+        <literal>org.hibernate.search.bridge.StringBridge</literal>
+        interface</para>
+
+        <programlisting>/**
+ * Padding Integer bridge.
+ * All numbers will be padded with 0 to match 5 digits
+ *
+ * @author Emmanuel Bernard
+ */
+public class PaddedIntegerBridge implements <emphasis role="bold">StringBridge</emphasis> {
+
+    private int PADDING = 5;
+
+    <emphasis role="bold">public String objectToString(Object object)</emphasis> {
+        String rawInteger = ( (Integer) object ).toString();
+        if (rawInteger.length() &gt; PADDING) throw new IllegalArgumentException( "Try to pad on a number too big" );
+        StringBuilder paddedInteger = new StringBuilder( );
+        for ( int padIndex = rawInteger.length() ; padIndex &lt; PADDING ; padIndex++ ) {
+            paddedInteger.append('0');
+        }
+        return paddedInteger.append( rawInteger ).toString();
+    }
+}</programlisting>
+
+        <para>Then any property or field can use this bridge thanks to the
+        <literal>@FieldBridge</literal> annotation</para>
+
+        <programlisting><emphasis role="bold">@FieldBridge(impl = PaddedIntegerBridge.class)</emphasis>
+private Integer length;</programlisting>
+
+        <para>Parameters can be passed to the Bridge implementation making it
+        more flexible. The Bridge implementation implements a
+        <classname>ParameterizedBridge</classname> interface, and the
+        parameters are passed through the <literal>@FieldBridge</literal>
+        annotation.</para>
+
+        <programlisting>public class PaddedIntegerBridge implements StringBridge, <emphasis
+            role="bold">ParameterizedBridge</emphasis> {
+
+    public static String PADDING_PROPERTY = "padding";
+    private int padding = 5; //default
+
+    <emphasis role="bold">public void setParameterValues(Map parameters)</emphasis> {
+        Object padding = parameters.get( PADDING_PROPERTY );
+        if (padding != null) this.padding = (Integer) padding;
+    }
+
+    public String objectToString(Object object) {
+        String rawInteger = ( (Integer) object ).toString();
+        if (rawInteger.length() &gt; padding) throw new IllegalArgumentException( "Try to pad on a number too big" );
+        StringBuilder paddedInteger = new StringBuilder( );
+        for ( int padIndex = rawInteger.length() ; padIndex &lt; padding ; padIndex++ ) {
+            paddedInteger.append('0');
+        }
+        return paddedInteger.append( rawInteger ).toString();
+    }
+}
+
+
+//property
+ at FieldBridge(impl = PaddedIntegerBridge.class, 
+        <emphasis role="bold">params = @Parameter(name="padding", value="10")</emphasis> )
+private Integer length;</programlisting>
+
+        <para>The <classname>ParameterizedBridge</classname> interface can be
+        implemented by <classname>StringBridge</classname>,
+        <classname>TwoWayStringBridge</classname>,
+        <classname>FieldBridge</classname> implementations (see
+        bellow).</para>
+
+        <para>If you expect to use your bridge implementation on for an id
+        property (ie annotated with <literal>@DocumentId</literal>), you need
+        to use a slightly extended version of <literal>StringBridge</literal>
+        named <classname>TwoWayStringBridge</classname>. <literal>Hibernate
+        Search</literal> needs to read the string representation of the
+        identifier and generate the object out of it. There is not difference
+        in the way the <literal>@FieldBridge</literal> annotation is
+        used.</para>
+
+        <programlisting>public class PaddedIntegerBridge implements TwoWayStringBridge, ParameterizedBridge {
+
+    public static String PADDING_PROPERTY = "padding";
+    private int padding = 5; //default
+
+    public void setParameterValues(Map parameters) {
+        Object padding = parameters.get( PADDING_PROPERTY );
+        if (padding != null) this.padding = (Integer) padding;
+    }
+
+    public String objectToString(Object object) {
+        String rawInteger = ( (Integer) object ).toString();
+        if (rawInteger.length() &gt; padding) throw new IllegalArgumentException( "Try to pad on a number too big" );
+        StringBuilder paddedInteger = new StringBuilder( );
+        for ( int padIndex = rawInteger.length() ; padIndex &lt; padding ; padIndex++ ) {
+            paddedInteger.append('0');
+        }
+        return paddedInteger.append( rawInteger ).toString();
+    }
+
+    <emphasis role="bold">public Object stringToObject(String stringValue)</emphasis> {
+        return new Integer(stringValue);
+    }
+}
+
+
+//id property
+ at DocumentId
+ at FieldBridge(impl = PaddedIntegerBridge.class,
+            params = @Parameter(name="padding", value="10") )
+private Integer id;</programlisting>
+
+        <para>It is critically important for the two-way process to be
+        idempotent (ie object = stringToObject( objectToString( object ) )
+        ).</para>
+      </section>
+
+      <section>
+        <title>FieldBridge</title>
+
+        <para>Some usecase requires more than a simple object to string
+        translation when mapping a property to a Lucene index. To give you
+        most of the flexibility you can also implement a bridge as a
+        <classname>FieldBridge</classname>. This interface give you a property
+        value and let you map it the way you want in your Lucene
+        <classname>Document</classname>.This interface is very similar in its
+        concept to the <productname>Hibernate</productname>
+        <classname>UserType</classname>.</para>
+
+        <para>You can for example store a given property in two different
+        document fields</para>
+
+        <programlisting>/**
+ * Store the date in 3 different field year, month, day
+ * to ease Range Query per year, month or day
+ * (eg get all the elements of december for the last 5 years)
+ *
+ * @author Emmanuel Bernard
+ */
+public class DateSplitBridge implements FieldBridge {
+    private final static TimeZone GMT = TimeZone.getTimeZone("GMT");
+
+    <emphasis role="bold">public void set(String name, Object value, Document document, Field.Store store, Field.Index index, Float boost) {</emphasis>
+        Date date = (Date) value;
+        Calendar cal = GregorianCalendar.getInstance( GMT );
+        cal.setTime( date );
+        int year = cal.get( Calendar.YEAR );
+        int month = cal.get( Calendar.MONTH ) + 1;
+        int day = cal.get( Calendar.DAY_OF_MONTH );
+        //set year
+        Field field = new Field( name + ".year", String.valueOf(year), store, index );
+        if ( boost != null ) field.setBoost( boost );
+        document.add( field );
+        //set month and pad it if needed
+        field = new Field( name + ".month", month &lt; 10 ? "0" : "" + String.valueOf(month), store, index );
+        if ( boost != null ) field.setBoost( boost );
+        document.add( field );
+        //set day and pad it if needed
+        field = new Field( name + ".day", day &lt; 10 ? "0" : "" + String.valueOf(day), store, index );
+        if ( boost != null ) field.setBoost( boost );
+        document.add( field );
+    }
+}
+
+
+//property
+<emphasis role="bold">@FieldBridge(impl = DateSplitBridge.class)</emphasis>
+private Integer length;</programlisting>
+
+        <para></para>
+      </section>
+    </section>
+  </section>
+
+  <section id="lucene-query">
+    <title>Querying</title>
+
+    <para>The second most important capability of <productname>Hibernate
+    Search</productname> is the ability to execute a Lucene query and retrieve
+    entities managed by an Hibernate session, providing the power of Lucene
+    without living the Hibernate paradygm, and giving another dimension to the
+    Hibernate classic search mechanisms (HQL, Criteria query, native SQL
+    query).</para>
+
+    <para>To access the <productname>Hibernate Search</productname> querying
+    facilities, you have to use an Hibernate
+    <classname>FullTextSession</classname>. A SearchSession wrap an regular
+    <classname>org.hibernate.Session</classname> to provide query and indexing
+    capabilities.</para>
+
+    <programlisting>Session session = sessionFactory.openSession();
+...
+FullTextSession fullTextSession = Search.createFullTextSession(session);</programlisting>
+
+    <para>The search facility is built on native Lucene queries.</para>
+
+    <programlisting>org.apache.lucene.QueryParser parser = new QueryParser("title", new StopAnalyzer() );
+
+org.hibernate.lucene.search.Query luceneQuery = parser.parse( "summary:Festina Or brand:Seiko" );
+<emphasis role="bold">org.hibernate.Query fullTextQuery = fullTextSession.createFullTextQuery( luceneQuery );</emphasis>
+
+List result = fullTextQuery.list(); //return a list of managed objects</programlisting>
+
+    <para>The Hibernate query built on top of the Lucene query is a regular
+    <literal>org.hibernate.Query</literal>, you are is the same paradygm as
+    the other Hibernate query facilities (HQL, Native or Criteria). The
+    regular <literal>list()</literal>, <literal>uniqueResult()</literal>,
+    <literal>iterate()</literal> and <literal>scroll()</literal> can be
+    used.</para>
+
+    <para>If you expect a reasonnable result number and expect to work on all
+    of them, <methodname>list()</methodname> or
+    <methodname>uniqueResult()</methodname> are recommanded.
+    <methodname>list()</methodname> work best if the entity
+    <literal>batch-size</literal> is set up properly. Note that Hibernate
+    Seach has to process all Lucene Hits elements when using
+    <methodname>list()</methodname>, <methodname>uniqueResult()</methodname>
+    and <methodname>iterate()</methodname>. If you wish to minimize Lucene
+    document loading, <methodname>scroll()</methodname> is more appropriate,
+    Don't forget to close the <classname>ScrollableResults</classname> object
+    when you're done, since it keeps Lucene resources.</para>
+
+    <para>An efficient way to work with queries is to use pagination. The
+    pagination API is exactly the one available in
+    <classname>org.hibernate.Query</classname>:</para>
+
+    <programlisting><emphasis role="bold">org.hibernate.Query fullTextQuery = fullTextSession.createFullTextQuery( luceneQuery );</emphasis>
+fullTextQuery.setFirstResult(30);
+fullTextQuery.setMaxResult(20);
+fullTextQuery.list(); //will return a list of 20 elements starting from the 30th</programlisting>
+
+    <para>Only the relevant Lucene Documents are accessed.</para>
+  </section>
+
+  <section id="lucene-index">
+    <title>Indexing</title>
+
+    <para>It is sometimes useful to index an object event if this object is
+    not inserted nor updated to the database. This is especially true when you
+    want to build your index the first time. You can achieve that goal using
+    the <classname>FullTextSession</classname>.</para>
+
+    <programlisting>FullTextSession fullTextSession = Search.createFullTextSession(session);
+Transaction tx = fullTextSession.beginTransaction();
+for (Customer customer : customers) {
+    <emphasis role="bold">fullTextSession.index(customer);</emphasis>
+}
+tx.commit(); //index are written at commit time</programlisting>
+
+    <para>For maximum efficiency, Hibernate Search batch index operations
+    which and execute them at commit time (Note: you don't need to use
+    <classname>org.hibernate.Transaction</classname> in a JTA
+    environment).</para>
+  </section>
 </chapter>
\ No newline at end of file

Added: branches/Branch_3_2/HibernateExt/metadata/lib/jta.jar
===================================================================
(Binary files differ)


Property changes on: branches/Branch_3_2/HibernateExt/metadata/lib/jta.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: branches/Branch_3_2/HibernateExt/metadata/lib/jta.licence.txt
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/lib/jta.licence.txt	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/lib/jta.licence.txt	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,49 @@
+  
+Sun Microsystems, Inc. 
+Binary Code License Agreement
+
+READ THE TERMS OF THIS AGREEMENT AND ANY PROVIDED SUPPLEMENTAL LICENSE TERMS (COLLECTIVELY "AGREEMENT") CAREFULLY BEFORE OPENING THE SOFTWARE MEDIA PACKAGE.  BY OPENING THE SOFTWARE MEDIA PACKAGE, YOU AGREE TO THE TERMS OF THIS AGREEMENT.  IF YOU ARE ACCESSING THE SOFTWARE ELECTRONICALLY, INDICATE YOUR ACCEPTANCE OF THESE TERMS BY SELECTING THE "ACCEPT" BUTTON AT THE END OF THIS AGREEMENT.  IF YOU DO NOT AGREE TO ALL THESE TERMS, PROMPTLY RETURN THE UNUSED SOFTWARE TO YOUR PLACE OF PURCHASE FOR A REFUND OR, IF THE SOFTWARE IS ACCESSED ELECTRONICALLY, SELECT THE "DECLINE" BUTTON AT THE END OF THIS AGREEMENT. 
+
+1.  LICENSE TO USE.  Sun grants you a non-exclusive and non-transferable license for the internal use only of the accompanying software and documentation and any error corrections provided by Sun (collectively "Software"), by the number of users and the class of computer hardware for which the corresponding fee has been paid. 
+
+2.  RESTRICTIONS.  Software is confidential and copyrighted. Title to Software and all associated intellectual property rights is retained by Sun and/or its licensors.  Except as specifically authorized in any Supplemental License Terms, you may not make copies of Software, other than a single copy of Software for archival purposes.  Unless enforcement is prohibited by applicable law, you may not modify, decompile, or reverse engineer Software.  Licensee acknowledges that Licensed Software is not designed or intended for use in the design, construction, operation or maintenance of any nuclear facility. Sun Microsystems, Inc. disclaims any express or implied warranty of fitness for such uses.   No right, title or interest in or to any trademark, service mark, logo or trade name of Sun or its licensors is granted under this Agreement. 
+
+3. LIMITED WARRANTY.  Sun warrants to you that for a period of ninety (90) days from the date of purchase, as evidenced by a copy of the receipt, the media on which Software is furnished (if any) will be free of defects in materials and workmanship under normal use.  Except for the foregoing, Software is provided "AS IS".  Your exclusive remedy and Sun's entire liability under this limited warranty will be at Sun's option to replace Software media or refund the fee paid for Software. 
+
+4.  DISCLAIMER OF WARRANTY.  UNLESS SPECIFIED IN THIS AGREEMENT, ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT ARE DISCLAIMED, EXCEPT TO THE EXTENT THAT THESE DISCLAIMERS ARE HELD TO BE LEGALLY INVALID. 
+
+5.  LIMITATION OF LIABILITY.  TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR SPECIAL, INDIRECT, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF OR RELATED TO THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.  In no event will Sun's liability to you, whether in contract, tort (including negligence), or otherwise, exceed the amount paid by you for Software under this Agreement.  The foregoing limitations will apply even if the above stated warranty fails of its essential purpose. 
+
+6.  Termination.  This Agreement is effective until terminated.  You may terminate this Agreement at any time by destroying all copies of Software.  This Agreement will terminate immediately without notice from Sun if you fail to comply with any provision of this Agreement.  Upon Termination, you must destroy all copies of Software. 
+
+7. Export Regulations. All Software and technical data delivered under this Agreement are subject to US export control laws and may be subject to export or import regulations in other countries.  You agree to comply strictly with all such laws and regulations and acknowledge that you have the responsibility to obtain such licenses to export, re-export, or import as may be required after delivery to you. 
+
+8.   U.S. Government Restricted Rights.  If Software is being acquired by or on behalf of the U.S. Government or by a U.S. Government prime contractor or subcontractor (at any tier), then the Government's rights in Software and accompanying documentation will be only as set forth in this Agreement; this is in accordance with 48 CFR 227.7201 through 227.7202-4 (for Department of Defense (DOD) acquisitions) and with 48 CFR 2.101 and 12.212 (for non-DOD acquisitions). 
+
+9.  Governing Law.  Any action related to this Agreement will be governed by California law and controlling U.S. federal law.  No choice of law rules of any jurisdiction will apply. 
+
+10.  Severability. If any provision of this Agreement is held to be unenforceable, this Agreement will remain in effect with the provision omitted, unless omission would frustrate the intent of the parties, in which case this Agreement will immediately terminate. 
+
+11.  Integration.  This Agreement is the entire agreement between you and Sun relating to its subject matter.  It supersedes all prior or contemporaneous oral or written communications, proposals, representations and warranties and prevails over any conflicting or additional terms of any quote, order, acknowledgment, or other communication between the parties relating to its subject matter during the term of this Agreement.  No modification of this Agreement will be binding, unless in writing and signed by an authorized representative of each party. 
+
+JAVATM INTERFACE CLASSES 
+JAVA TRANSACTION API (JTA), VERSION 1.0.1B, MAINTENANCE RELEASE
+SUPPLEMENTAL LICENSE TERMS
+
+These supplemental license terms ("Supplemental Terms") add to or modify the terms of the Binary Code License Agreement (collectively, the "Agreement"). Capitalized terms not defined in these Supplemental Terms shall have the same meanings ascribed to them in the Agreement. These Supplemental Terms shall supersede any inconsistent or conflicting terms in the Agreement, or in any license contained within the Software. 
+
+1. Software Internal Use and Development License Grant. Subject to the terms and conditions of this Agreement, including, but not limited to Section 3 (Java Technology Restrictions) of these Supplemental Terms, Sun grants you a non-exclusive, non-transferable, limited license to reproduce internally and use internally the binary form of the Software, complete and unmodified, for the sole purpose of designing, developing and testing your Java applets and applications ("Programs"). 
+
+2. License to Distribute Software.  In addition to the license granted in Section 1 (Software Internal Use and Development License Grant) of these Supplemental Terms, subject to the terms and conditions of this Agreement, including but not limited to Section 3 (Java Technology Restrictions), Sun grants you a non-exclusive, non-transferable, limited license to reproduce and distribute the Software in binary form only, provided that you (i) distribute the Software complete and unmodified and only bundled as part of your Programs, (ii) do not distribute additional software intended to replace any component(s) of the Software, (iii) do not remove or alter any proprietary legends or notices contained in the Software, (iv) only distribute the Software subject to a license agreement that protects Sun's interests consistent with the terms contained in this Agreement, and (v) agree to defend and indemnify Sun and its licensors from and against any damages, costs, liabilities, settle!
 ment amounts and/or expenses (including attorneys' fees) incurred in connection with any claim, lawsuit or action by any third party that arises or results from the use or distribution of any and all Programs and/or Software. 
+
+3. Java Technology Restrictions. You may not modify the Java Platform Interface ("JPI", identified as classes contained within the "java" package or any subpackages of the "java" package), by creating additional classes within the JPI or otherwise causing the addition to or modification of the classes in the JPI.  In the event that you create an additional class and associated API(s) which (i) extends the functionality of the Java Platform, and (ii) is exposed to third party software developers for the purpose of developing additional software which invokes such additional API, you must promptly publish broadly an accurate specification for such API for free use by all developers. You may not create, or authorize your licensees to create additional classes, interfaces, or subpackages that are in any way identified as "java", "javax", "sun" or similar convention as specified by Sun in any naming convention designation. 
+
+4. Trademarks and Logos. You acknowledge and agree as between you and Sun that Sun owns the SUN, SOLARIS, JAVA, JINI, FORTE, and iPLANET trademarks and all SUN, SOLARIS, JAVA, JINI, FORTE, and iPLANET-related trademarks, service marks, logos and other brand designations ("Sun Marks"), and you agree to comply with the Sun Trademark and Logo Usage Requirements currently located at http://www.sun.com/policies/trademarks. Any use you make of the Sun Marks inures to Sun's benefit. 
+
+5. Source Code. Software may contain source code that is provided solely for reference purposes pursuant to the terms of this Agreement.  Source code may not be redistributed unless expressly provided for in this Agreement. 
+
+6. Termination for Infringement.  Either party may terminate this Agreement immediately should any Software become, or in either party's opinion be likely to become, the subject of a claim of infringement of any intellectual property right. 
+
+For inquiries please contact: Sun Microsystems, Inc. 4150 Network Circle, Santa Clara, California 95054. 
+
+ 

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/Environment.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/Environment.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/Environment.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,17 @@
+//$Id: Environment.java 10742 2006-11-07 01:03:16Z epbernard $
+package org.hibernate.search;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public final class Environment {
+	/**
+	 * Indexes base directory
+	 */
+	public static final String INDEX_BASE_DIR = "hibernate.search.index_dir";
+
+	/**
+	 * Lucene analyser
+	 */
+	public static final String ANALYZER_CLASS = "hibernate.search.analyzer";
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/FullTextSession.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/FullTextSession.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/FullTextSession.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,25 @@
+//$Id: $
+package org.hibernate.search;
+
+import org.hibernate.classic.Session;
+import org.hibernate.Query;
+
+/**
+ * Extends the Hibernate {@link Session} with Full text search and indexing capabilities
+ *
+ * @author Emmanuel Bernard
+ */
+public interface FullTextSession extends Session {
+	/**
+	 * Create a Query on top of a native Lucene Query returning the matching objects
+	 * of type <code>entities</code> and their respective subclasses.
+	 * If no entity is provided, no type filtering is done.
+	 */
+	Query createFullTextQuery(org.apache.lucene.search.Query luceneQuery, Class... entities);
+
+	/**
+	 * Force the (re)indexing of a given <b>managed</b> object.
+	 * Indexation is batched per transaction
+	 */
+	void index(Object entity);
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/Search.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/Search.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/Search.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,19 @@
+//$Id: $
+package org.hibernate.search;
+
+import org.hibernate.Session;
+import org.hibernate.search.impl.FullTextSessionImpl;
+
+/**
+ * Helper class to get a FullTextSession out of a regular session
+ * @author Emmanuel Bernard
+ */
+public final class Search {
+
+	private Search() {
+	}
+
+	public static FullTextSession createFullTextSession(Session session) {
+		return new FullTextSessionImpl(session);
+	}
+}
\ No newline at end of file

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Boost.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Boost.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Boost.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,20 @@
+//$Id: $
+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;
+
+/**
+ * Apply a boost factor on a field or a whole entity
+ *
+ * @author Emmanuel Bernard
+ */
+ at Retention( RetentionPolicy.RUNTIME )
+ at Target( {ElementType.TYPE, ElementType.METHOD, ElementType.FIELD} )
+ at Documented
+public @interface Boost {
+	float value();
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/DateBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/DateBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/DateBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,25 @@
+//$Id: $
+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;
+
+import org.hibernate.search.annotations.Resolution;
+
+/**
+ * Defines the temporal resolution of a given field
+ * Date are stored as String in GMT
+ *
+ * @author Emmanuel Bernard
+ */
+ at Retention( RetentionPolicy.RUNTIME )
+ at Target( {ElementType.FIELD, ElementType.METHOD} )
+ at Documented
+//TODO allow pattern like yyyyMMdd?
+//TODO allow base timezone?
+public @interface DateBridge {
+	Resolution resolution();
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/DocumentId.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/DocumentId.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/DocumentId.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,22 @@
+//$Id: $
+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;
+
+/**
+ * Declare a field as the document id. If set to a property, the property will be used
+ * TODO: If set to a class, the class itself will be passed to the FieldBridge
+ * Note that @{link org.hibernate.search.bridge.FieldBridge#get} must return the Entity id
+ *
+ * @author Emmanuel Bernard
+ */
+ at Retention( RetentionPolicy.RUNTIME )
+ at Target( {ElementType.METHOD, ElementType.FIELD} )
+ at Documented
+public @interface DocumentId {
+	String name() default "";
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Field.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Field.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Field.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,38 @@
+//$Id: $
+/**
+ * JavaDoc copy/pastle from the Apache Lucene project
+ * Available under the ASL 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+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;
+
+/**
+ * Mark a property as indexable
+ *
+ * @author Emmanuel Bernard
+ */
+ at Retention( RetentionPolicy.RUNTIME )
+ at Target( {ElementType.METHOD, ElementType.FIELD} )
+ at Documented
+public @interface Field {
+	/**
+	 * Field name, default to the JavaBean property name
+	 */
+	String name() default "";
+
+	/**
+	 * Should the value be stored in the document
+	 */
+	Store store() default Store.NO;
+
+	/**
+	 * Defines how the Field should be indexed
+	 */
+	Index index();
+
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/FieldBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/FieldBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/FieldBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,24 @@
+//$Id: $
+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;
+
+import org.hibernate.annotations.Parameter;
+
+/**
+ * specifies a given field bridge implementation
+ *
+ * @author Emmanuel Bernard
+ */
+ at Retention( RetentionPolicy.RUNTIME )
+ at Target( {ElementType.FIELD, ElementType.METHOD} )
+ at Documented
+public @interface FieldBridge {
+	public Class impl() default void.class;
+
+	public Parameter[] params() default {};
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Index.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Index.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Index.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,34 @@
+//$Id: $
+package org.hibernate.search.annotations;
+
+/**
+ * Defines how an Field should be indexed
+ */
+public enum Index {
+	/**
+	 * Do not index the field value. This field can thus not be searched,
+	 * but one can still access its contents provided it is
+	 * {@link Store stored}.
+	 */
+	NO,
+	/**
+	 * Index the field's value so it can be searched. An Analyzer will be used
+	 * to tokenize and possibly further normalize the text before its
+	 * terms will be stored in the index. This is useful for common text.
+	 */
+	TOKENIZED,
+	/**
+	 * Index the field's value without using an Analyzer, so it can be searched.
+	 * As no analyzer is used the value will be stored as a single term. This is
+	 * useful for unique Ids like product numbers.
+	 */
+	UN_TOKENISED,
+	/**
+	 * Index the field's value without an Analyzer, and disable
+	 * the storing of norms.  No norms means that index-time boosting
+	 * and field length normalization will be disabled.  The benefit is
+	 * less memory usage as norms take up one byte per indexed field
+	 * for every document in the index.
+	 */
+	NO_NORMS
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Indexed.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Indexed.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Indexed.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,21 @@
+//$Id: Indexed.java 10742 2006-11-07 01:03:16Z epbernard $
+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;
+
+ at Retention( RetentionPolicy.RUNTIME )
+ at Target( ElementType.TYPE )
+ at Documented
+/**
+ * Specifies that an entity is to be indexed by Lucene
+ */
+public @interface Indexed {
+	/**
+	 * The filename of the index
+	 */
+	String index() default "";
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Keyword.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Keyword.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Keyword.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,29 @@
+//$Id: Keyword.java 10742 2006-11-07 01:03:16Z epbernard $
+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;
+
+ at Retention( RetentionPolicy.RUNTIME )
+ at Target( {ElementType.METHOD, ElementType.FIELD} )
+ at Documented
+/**
+ * Specifies that a property of an entity is a Lucene
+ * keyword field
+ * @deprecated use @Field(index=Index.UN_TOKENIZED, store=Store.YES) or @DocumentId when id=true was used
+ */
+ at Deprecated
+public @interface Keyword {
+	/**
+	 * The field name
+	 */
+	String name() default "";
+
+	/**
+	 * Specifies that this is the "identifier" keyword
+	 */
+	boolean id() default false;
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Resolution.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Resolution.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Resolution.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,17 @@
+//$Id: $
+package org.hibernate.search.annotations;
+
+/**
+ * Date indexing resolution
+ *
+ * @author Emmanuel Bernard
+ */
+public enum Resolution {
+	YEAR,
+	MONTH,
+	DAY,
+	HOUR,
+	MINUTE,
+	SECOND,
+	MILLISECOND
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Store.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Store.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Store.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,13 @@
+//$Id: $
+package org.hibernate.search.annotations;
+
+/**
+ * Whether or not the value is stored in the document
+ *
+ * @author Emmanuel Bernard
+ */
+public enum Store {
+	NO,
+	YES,
+	COMPRESS
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Text.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Text.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Text.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,24 @@
+//$Id: Text.java 10742 2006-11-07 01:03:16Z epbernard $
+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;
+
+ at Retention( RetentionPolicy.RUNTIME )
+ at Target( {ElementType.METHOD, ElementType.FIELD} )
+ at Documented
+/**
+ * Specifies that a property of an entity is a Lucene
+ * text field
+ * @deprecated use @Field(index=Index.TOKENIZED, store=Store.YES)
+ */
+ at Deprecated
+public @interface Text {
+	/**
+	 * The field name
+	 */
+	String name() default "";
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Unstored.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Unstored.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/annotations/Unstored.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,24 @@
+//$Id: Unstored.java 10742 2006-11-07 01:03:16Z epbernard $
+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;
+
+ at Retention( RetentionPolicy.RUNTIME )
+ at Target( {ElementType.METHOD, ElementType.FIELD} )
+ at Documented
+/**
+ * Specifies that a property of an entity is a Lucene
+ * unstored field
+ * @deprecated use @Field(index=Index.TOKENIZED, store=Store.NO)
+ */
+ at Deprecated
+public @interface Unstored {
+	/**
+	 * The field name
+	 */
+	String name() default "";
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/AddWork.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/AddWork.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/AddWork.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,15 @@
+//$Id: $
+package org.hibernate.search.backend;
+
+import java.io.Serializable;
+
+import org.apache.lucene.document.Document;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class AddWork extends Work {
+	public AddWork(Serializable id, Class entity, Document document) {
+		super( id, entity, document );
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/DeleteWork.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/DeleteWork.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/DeleteWork.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,13 @@
+//$Id: $
+package org.hibernate.search.backend;
+
+import java.io.Serializable;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class DeleteWork extends Work {
+	public DeleteWork(Serializable id, Class entity) {
+		super( id, entity );
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/UpdateWork.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/UpdateWork.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/UpdateWork.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,15 @@
+//$Id: $
+package org.hibernate.search.backend;
+
+import java.io.Serializable;
+
+import org.apache.lucene.document.Document;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class UpdateWork extends Work {
+	public UpdateWork(Serializable id, Class entity, Document document) {
+		super( id, entity, document );
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/Work.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/Work.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/Work.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,40 @@
+//$Id: $
+package org.hibernate.search.backend;
+
+import java.io.Serializable;
+
+import org.apache.lucene.document.Document;
+
+/**
+ * Represent a Lucene unit work
+ *
+ * @author Emmanuel Bernard
+ */
+public abstract class Work implements Serializable {
+	private Document document;
+	private Class entity;
+	private Serializable id;
+
+	public Work(Serializable id, Class entity) {
+		this( id, entity, null );
+	}
+
+	public Work(Serializable id, Class entity, Document document) {
+		this.id = id;
+		this.entity = entity;
+		this.document = document;
+	}
+
+
+	public Document getDocument() {
+		return document;
+	}
+
+	public Class getEntity() {
+		return entity;
+	}
+
+	public Serializable getId() {
+		return id;
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/WorkQueue.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/WorkQueue.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/WorkQueue.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,24 @@
+//$Id: $
+package org.hibernate.search.backend;
+
+/**
+ * Set of work operations
+ *
+ * @author Emmanuel Bernard
+ */
+public interface WorkQueue {
+	/**
+	 * Add a work
+	 */
+	void add(Work work);
+
+	/**
+	 * Execute works
+	 */
+	void performWork();
+
+	/**
+	 * Rollback works
+	 */
+	void cancelWork();
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/Workspace.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/Workspace.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/Workspace.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,137 @@
+//$Id: $
+package org.hibernate.search.backend;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.hibernate.HibernateException;
+import org.hibernate.search.engine.DocumentBuilder;
+import org.hibernate.search.store.DirectoryProvider;
+
+/**
+ * Lucene workspace
+ * This is not intended to be used in a multithreaded environment
+ * <p/>
+ * One cannot execute modification through an IndexReader when an IndexWriter has been acquired on the same underlying directory
+ * One cannot get an IndexWriter when an IndexReader have been acquired and modificed on the same underlying directory
+ * The recommended approach is to execute all the modifications on the IndexReaders, {@link #clean()} }, and acquire the
+ * index writers
+ *
+ * @author Emmanuel Bernard
+ */
+//TODO introduce the notion of read only IndexReader? We cannot enforce it because Lucene use abstract classes, not interfaces
+public class Workspace {
+	private static Log log = LogFactory.getLog( Workspace.class );
+	private Map<Class, DocumentBuilder<Object>> documentBuilders;
+	private Map<DirectoryProvider, ReentrantLock> lockableDirectoryProviders;
+	private Map<DirectoryProvider, IndexReader> readers = new HashMap<DirectoryProvider, IndexReader>();
+	private Map<DirectoryProvider, IndexWriter> writers = new HashMap<DirectoryProvider, IndexWriter>();
+	private List<DirectoryProvider> lockedProviders = new ArrayList<DirectoryProvider>();
+
+	public Workspace(Map<Class, DocumentBuilder<Object>> documentBuilders,
+					 Map<DirectoryProvider, ReentrantLock> lockableDirectoryProviders) {
+		this.documentBuilders = documentBuilders;
+		this.lockableDirectoryProviders = lockableDirectoryProviders;
+	}
+
+
+	public DocumentBuilder getDocumentBuilder(Class entity) {
+		return documentBuilders.get( entity );
+	}
+
+	public IndexReader getIndexReader(Class entity) {
+		//TODO NPEs
+		DirectoryProvider provider = documentBuilders.get( entity ).getDirectoryProvider();
+		IndexReader reader = readers.get( provider );
+		if ( reader != null ) return reader;
+		lockProvider( provider );
+		try {
+			reader = IndexReader.open( provider.getDirectory() );
+			readers.put( provider, reader );
+		}
+		catch (IOException e) {
+			cleanUp( new HibernateException( "Unable to open IndexReader for " + entity, e ) );
+		}
+		return reader;
+	}
+
+	public IndexWriter getIndexWriter(Class entity) {
+		DirectoryProvider provider = documentBuilders.get( entity ).getDirectoryProvider();
+		IndexWriter writer = writers.get( provider );
+		if ( writer != null ) return writer;
+		lockProvider( provider );
+		try {
+			writer = new IndexWriter(
+					provider.getDirectory(), documentBuilders.get( entity ).getAnalyzer(), false
+			); //have been created at init time
+			writers.put( provider, writer );
+		}
+		catch (IOException e) {
+			cleanUp( new HibernateException( "Unable to open IndexWriter for " + entity, e ) );
+		}
+		return writer;
+	}
+
+	private void lockProvider(DirectoryProvider provider) {
+		//make sure to use a semaphore
+		ReentrantLock lock = lockableDirectoryProviders.get( provider );
+		//of course a given thread cannot have a race cond with itself
+		if ( !lock.isHeldByCurrentThread() ) {
+			lock.lock();
+			lockedProviders.add( provider );
+		}
+	}
+
+	private void cleanUp(HibernateException originalException) {
+		//release all readers and writers, then reelase locks
+		HibernateException raisedException = originalException;
+		for ( IndexReader reader : readers.values() ) {
+			try {
+				reader.close();
+			}
+			catch (IOException e) {
+				if ( raisedException != null ) {
+					log.error( "Subsequent Exception while closing IndexReader", e );
+				}
+				else {
+					raisedException = new HibernateException( "Exception while closing IndexReader", e );
+				}
+			}
+		}
+		for ( IndexWriter writer : writers.values() ) {
+			try {
+				writer.close();
+			}
+			catch (IOException e) {
+				if ( raisedException != null ) {
+					log.error( "Subsequent Exception while closing IndexWriter", e );
+				}
+				else {
+					raisedException = new HibernateException( "Exception while closing IndexWriter", e );
+				}
+			}
+		}
+		for ( DirectoryProvider provider : lockedProviders ) {
+			lockableDirectoryProviders.get( provider ).unlock();
+		}
+		readers.clear();
+		writers.clear();
+		lockedProviders.clear();
+		if ( raisedException != null ) throw raisedException;
+	}
+
+	/**
+	 * release resources consumed in the workspace if any
+	 */
+	public void clean() {
+		cleanUp( null );
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/BatchLuceneWorkQueue.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/BatchLuceneWorkQueue.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/BatchLuceneWorkQueue.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,65 @@
+//$Id: $
+package org.hibernate.search.backend.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.hibernate.search.engine.DocumentBuilder;
+import org.hibernate.search.store.DirectoryProvider;
+import org.hibernate.search.backend.impl.LuceneWorker;
+import org.hibernate.search.backend.WorkQueue;
+import org.hibernate.search.backend.Workspace;
+import org.hibernate.search.backend.Work;
+import org.hibernate.search.backend.UpdateWork;
+import org.hibernate.search.backend.DeleteWork;
+import org.hibernate.search.backend.AddWork;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class BatchLuceneWorkQueue implements WorkQueue {
+	private Workspace workspace;
+	private LuceneWorker worker;
+	private List<Work> queue = new ArrayList<Work>();
+
+	public BatchLuceneWorkQueue(Map<Class, DocumentBuilder<Object>> documentBuilders,
+					 Map<DirectoryProvider, ReentrantLock> lockableDirectoryProviders) {
+		workspace = new Workspace( documentBuilders, lockableDirectoryProviders );
+		worker = new LuceneWorker( workspace );
+	}
+
+	public void add(Work work) {
+		//TODO optimize by getting rid of dupe works
+		if ( work instanceof UpdateWork ) {
+			//split in 2 to optimize the process (reader first, writer next
+			queue.add( new DeleteWork( work.getId(), work.getEntity() ) );
+			queue.add( new AddWork( work.getId(), work.getEntity(), work.getDocument() ) );
+		}
+		else {
+			queue.add( work );
+		}
+	}
+
+	public void performWork() {
+		try {
+			//use of index reader
+			for ( Work work : queue ) {
+				if ( work instanceof DeleteWork ) worker.performWork( work );
+			}
+			workspace.clean(); //close readers
+			for ( Work work : queue ) {
+				if ( work instanceof AddWork ) worker.performWork( work );
+			}
+		}
+		finally {
+			workspace.clean();
+			queue.clear();
+		}
+	}
+
+	public void cancelWork() {
+		queue.clear();
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/LuceneWorker.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/LuceneWorker.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/LuceneWorker.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,116 @@
+//$Id: $
+package org.hibernate.search.backend.impl;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.hibernate.AssertionFailure;
+import org.hibernate.HibernateException;
+import org.hibernate.search.engine.DocumentBuilder;
+import org.hibernate.search.backend.Workspace;
+import org.hibernate.search.backend.DeleteWork;
+import org.hibernate.search.backend.AddWork;
+import org.hibernate.search.backend.UpdateWork;
+import org.hibernate.search.backend.Work;
+
+/**
+ * Stateless implementation that perform a work
+ *
+ * @author Emmanuel Bernard
+ */
+public class LuceneWorker {
+	private Workspace workspace;
+	private static Log log = LogFactory.getLog( LuceneWorker.class );
+
+	public LuceneWorker(Workspace workspace) {
+		this.workspace = workspace;
+	}
+
+	public void performWork(Work work) {
+		if ( AddWork.class.isAssignableFrom( work.getClass() ) ) {
+			performWork( (AddWork) work );
+		}
+		else if ( UpdateWork.class.isAssignableFrom( work.getClass() ) ) {
+			performWork( (UpdateWork) work );
+		}
+		else if ( DeleteWork.class.isAssignableFrom( work.getClass() ) ) {
+			performWork( (DeleteWork) work );
+		}
+		else {
+			throw new AssertionFailure( "Unknown work type: " + work.getClass() );
+		}
+	}
+
+	public void performWork(AddWork work) {
+		Class entity = work.getEntity();
+		Serializable id = work.getId();
+		Document document = work.getDocument();
+		add( entity, id, document );
+	}
+
+	private void add(Class entity, Serializable id, Document document) {
+		if ( log.isTraceEnabled() )
+			log.trace( "add to Lucene index: " + entity + "#" + id + ": " + document );
+		IndexWriter writer = workspace.getIndexWriter( entity );
+		try {
+			writer.addDocument( document );
+		}
+		catch (IOException e) {
+			throw new HibernateException( "Unable to add to Lucene index: " + entity + "#" + id, e );
+		}
+	}
+
+	public void performWork(UpdateWork work) {
+		Class entity = work.getEntity();
+		Serializable id = work.getId();
+		Document document = work.getDocument();
+		remove( entity, id );
+		add( entity, id, document );
+	}
+
+	public void performWork(DeleteWork work) {
+		Class entity = work.getEntity();
+		Serializable id = work.getId();
+		remove( entity, id );
+	}
+
+	private void remove(Class entity, Serializable id) {
+		log.trace( "remove from Lucene index: " + entity + "#" + id );
+		DocumentBuilder builder = workspace.getDocumentBuilder( entity );
+		Term term = builder.getTerm( id );
+		IndexReader reader = workspace.getIndexReader( entity );
+		TermDocs termDocs = null;
+		try {
+			//TODO is there a faster way?
+			//TODO include TermDocs into the workspace?
+			termDocs = reader.termDocs( term );
+			String entityName = entity.getName();
+			while ( termDocs.next() ) {
+				int docIndex = termDocs.doc();
+				if ( entityName.equals( reader.document( docIndex ).get( DocumentBuilder.CLASS_FIELDNAME ) ) ) {
+					//remove only the one of the right class
+					//loop all to remove all the matches (defensive code)
+					reader.deleteDocument( docIndex );
+				}
+			}
+		}
+		catch (Exception e) {
+			throw new HibernateException( "Unable to remove from Lucene index: " + entity + "#" + id, e );
+		}
+		finally {
+			if (termDocs != null) try {
+				termDocs.close();
+			}
+			catch (IOException e) {
+				log.warn( "Unable to close termDocs properly", e);
+			}
+		}
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/PostTransactionWorkQueueSynchronization.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/PostTransactionWorkQueueSynchronization.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/PostTransactionWorkQueueSynchronization.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,47 @@
+//$Id: $
+package org.hibernate.search.backend.impl;
+
+import javax.transaction.Status;
+import javax.transaction.Synchronization;
+
+import org.hibernate.search.backend.WorkQueue;
+import org.hibernate.search.backend.Work;
+
+/**
+ * Execute some work inside a transaction sychronization
+ *
+ * @author Emmanuel Bernard
+ */
+public class PostTransactionWorkQueueSynchronization implements Synchronization {
+	private WorkQueue workQueue;
+	private boolean consumed;
+
+	public PostTransactionWorkQueueSynchronization(WorkQueue workQueue) {
+		this.workQueue = workQueue;
+	}
+
+	public void add(Work work) {
+		workQueue.add( work );
+	}
+
+	public boolean isConsumed() {
+		return consumed;
+	}
+
+	public void beforeCompletion() {
+	}
+
+	public void afterCompletion(int i) {
+		try {
+			if ( Status.STATUS_COMMITTED == i ) {
+				workQueue.performWork();
+			}
+			else {
+				workQueue.cancelWork();
+			}
+		}
+		finally {
+			consumed = true;
+		}
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/BridgeFactory.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/BridgeFactory.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/BridgeFactory.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,141 @@
+//$Id: $
+package org.hibernate.search.bridge;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.hibernate.HibernateException;
+import org.hibernate.AssertionFailure;
+import org.hibernate.search.bridge.builtin.DateBridge;
+import org.hibernate.search.bridge.builtin.FloatBridge;
+import org.hibernate.search.bridge.builtin.LongBridge;
+import org.hibernate.search.bridge.builtin.BigIntegerBridge;
+import org.hibernate.search.bridge.builtin.StringBridge;
+import org.hibernate.search.bridge.builtin.IntegerBridge;
+import org.hibernate.search.bridge.builtin.BigDecimalBridge;
+import org.hibernate.search.bridge.builtin.DoubleBridge;
+import org.hibernate.search.bridge.builtin.ShortBridge;
+import org.hibernate.search.annotations.Resolution;
+import org.hibernate.annotations.Parameter;
+import org.hibernate.reflection.XClass;
+import org.hibernate.reflection.XMember;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class BridgeFactory {
+	private static Map<String, FieldBridge> builtInBridges = new HashMap<String, FieldBridge>();
+
+	private BridgeFactory() {
+	}
+
+	public static final TwoWayFieldBridge DOUBLE = new TwoWayString2FieldBridgeAdaptor( new DoubleBridge() );
+
+	public static final TwoWayFieldBridge FLOAT = new TwoWayString2FieldBridgeAdaptor( new FloatBridge() );
+
+	public static final TwoWayFieldBridge SHORT = new TwoWayString2FieldBridgeAdaptor( new ShortBridge() );
+
+	public static final TwoWayFieldBridge INTEGER = new TwoWayString2FieldBridgeAdaptor( new IntegerBridge() );
+
+	public static final TwoWayFieldBridge LONG = new TwoWayString2FieldBridgeAdaptor( new LongBridge() );
+
+	public static final TwoWayFieldBridge BIG_INTEGER = new TwoWayString2FieldBridgeAdaptor( new BigIntegerBridge() );
+
+	public static final TwoWayFieldBridge BIG_DECIMAL = new TwoWayString2FieldBridgeAdaptor( new BigDecimalBridge() );
+
+	public static final TwoWayFieldBridge STRING = new TwoWayString2FieldBridgeAdaptor( new StringBridge() );
+
+	public static final FieldBridge DATE_YEAR = new String2FieldBridgeAdaptor( DateBridge.DATE_YEAR );
+	public static final FieldBridge DATE_MONTH = new String2FieldBridgeAdaptor( DateBridge.DATE_MONTH );
+	public static final FieldBridge DATE_DAY = new String2FieldBridgeAdaptor( DateBridge.DATE_DAY );
+	public static final FieldBridge DATE_HOUR = new String2FieldBridgeAdaptor( DateBridge.DATE_HOUR );
+	public static final FieldBridge DATE_MINUTE = new String2FieldBridgeAdaptor( DateBridge.DATE_MINUTE );
+	public static final FieldBridge DATE_SECOND = new String2FieldBridgeAdaptor( DateBridge.DATE_SECOND );
+	public static final TwoWayFieldBridge DATE_MILLISECOND = new TwoWayString2FieldBridgeAdaptor( DateBridge.DATE_MILLISECOND );
+
+	static {
+		builtInBridges.put( Double.class.getName(), DOUBLE );
+		builtInBridges.put( double.class.getName(), DOUBLE );
+		builtInBridges.put( Float.class.getName(), FLOAT );
+		builtInBridges.put( float.class.getName(), FLOAT );
+		builtInBridges.put( Short.class.getName(), SHORT );
+		builtInBridges.put( short.class.getName(), SHORT );
+		builtInBridges.put( Integer.class.getName(), INTEGER );
+		builtInBridges.put( int.class.getName(), INTEGER );
+		builtInBridges.put( Long.class.getName(), LONG );
+		builtInBridges.put( long.class.getName(), LONG );
+		builtInBridges.put( BigInteger.class.getName(), BIG_INTEGER );
+		builtInBridges.put( BigDecimal.class.getName(), BIG_DECIMAL );
+		builtInBridges.put( String.class.getName(), STRING );
+
+		builtInBridges.put( Date.class.getName(), DATE_MILLISECOND );
+	}
+
+	public static FieldBridge guessType(XMember member) {
+		FieldBridge bridge = null;
+		org.hibernate.search.annotations.FieldBridge bridgeAnn =
+				member.getAnnotation( org.hibernate.search.annotations.FieldBridge.class );
+		if ( bridgeAnn != null ) {
+			Class impl = bridgeAnn.impl();
+			try {
+				Object instance = impl.newInstance();
+				if ( FieldBridge.class.isAssignableFrom( impl ) ) {
+					bridge = (FieldBridge) instance;
+				}
+				else if ( org.hibernate.search.bridge.TwoWayStringBridge.class.isAssignableFrom( impl ) ) {
+					bridge = new TwoWayString2FieldBridgeAdaptor( (org.hibernate.search.bridge.TwoWayStringBridge) instance );
+				}
+				else if ( org.hibernate.search.bridge.StringBridge.class.isAssignableFrom( impl ) ) {
+					bridge = new String2FieldBridgeAdaptor( (org.hibernate.search.bridge.StringBridge) instance );
+				}
+				if ( bridgeAnn.params().length > 0 && ParameterizedBridge.class.isAssignableFrom( impl ) ) {
+					Map params = new HashMap( bridgeAnn.params().length );
+					for ( Parameter param : bridgeAnn.params() ) {
+						params.put( param.name(), param.value() );
+					}
+					( (ParameterizedBridge) instance ).setParameterValues( params );
+				}
+			}
+			catch (Exception e) {
+				//TODO add classname
+				throw new HibernateException( "Unable to instanciate FieldBridge for " + member.getName(), e );
+			}
+		}
+		else if ( member.isAnnotationPresent( org.hibernate.search.annotations.DateBridge.class ) ) {
+			Resolution resolution = member.getAnnotation( org.hibernate.search.annotations.DateBridge.class ).resolution();
+			bridge = getDateField( resolution );
+		}
+		else {
+			//find in built-ins
+			XClass returnType = member.getType();
+			bridge = builtInBridges.get( returnType.getName() );
+		}
+		//TODO add classname
+		if ( bridge == null ) throw new HibernateException( "Unable to guess FieldBridge for " + member.getName() );
+		return bridge;
+	}
+
+	public static FieldBridge getDateField(Resolution resolution) {
+		switch (resolution) {
+			case YEAR:
+				return DATE_YEAR;
+			case MONTH:
+				return DATE_MONTH;
+			case DAY:
+				return DATE_DAY;
+			case HOUR:
+				return DATE_HOUR;
+			case MINUTE:
+				return DATE_MINUTE;
+			case SECOND:
+				return DATE_SECOND;
+			case MILLISECOND:
+				return DATE_MILLISECOND;
+			default:
+				throw new AssertionFailure( "Unknown Resolution: " + resolution );
+		}
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/FieldBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/FieldBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/FieldBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,23 @@
+//$Id: $
+package org.hibernate.search.bridge;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+
+/**
+ * Link between a java property and a Lucene Document
+ * Usually a Java property will be linked to a Document Field
+ *
+ * @author Emmanuel Bernard
+ */
+//TODO should show Field or document?
+//document is nice since I can save an object into several fields
+public interface FieldBridge {
+	/**
+	 * Manipulate the document to index the given value.
+	 * A common implementation is to add a Field <code>name</code> to the given document following
+	 * the parameters (<code>store</code>, <code>index</code>, <code>boost</code>) if the
+	 * <code>value</code> is not null
+	 */
+	void set(String name, Object value, Document document, Field.Store store, Field.Index index, Float boost);
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/ParameterizedBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/ParameterizedBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/ParameterizedBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,14 @@
+//$Id: $
+package org.hibernate.search.bridge;
+
+import java.util.Map;
+
+/**
+ * Allow parameter injection to a given bridge
+ *
+ * @author Emmanuel Bernard
+ */
+public interface ParameterizedBridge {
+	//TODO inject Properties? since the annotations cannot support Object attribute?
+	void setParameterValues(Map parameters);
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/String2FieldBridgeAdaptor.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/String2FieldBridgeAdaptor.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/String2FieldBridgeAdaptor.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,30 @@
+//$Id: $
+package org.hibernate.search.bridge;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.hibernate.util.StringHelper;
+
+/**
+ * Bridge to use a StringBridge as a FieldBridge
+ *
+ * @author Emmanuel Bernard
+ */
+public class String2FieldBridgeAdaptor implements FieldBridge {
+	private StringBridge stringBridge;
+
+	public String2FieldBridgeAdaptor(StringBridge stringBridge) {
+		this.stringBridge = stringBridge;
+	}
+
+	public void set(String name, Object value, Document document, Field.Store store, Field.Index index, Float boost) {
+		String indexedString = stringBridge.objectToString( value );
+		//Do not add fields on empty strings, seems a sensible default in most situations
+		if ( StringHelper.isNotEmpty( indexedString ) ) {
+			Field field = new Field( name, indexedString, store, index );
+			if ( boost != null ) field.setBoost( boost );
+			document.add( field );
+		}
+	}
+
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/StringBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/StringBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/StringBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,16 @@
+//$Id: $
+package org.hibernate.search.bridge;
+
+/**
+ * Transform an object into a string representation
+ *
+ * @author Emmanuel Bernard
+ */
+public interface StringBridge {
+	
+	/**
+	 * convert the object representation to a String
+	 * The return String must not be null, it can be empty though
+	 */
+	String objectToString(Object object);
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/TwoWayFieldBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/TwoWayFieldBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/TwoWayFieldBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,32 @@
+//$Id: $
+package org.hibernate.search.bridge;
+
+import org.apache.lucene.document.Document;
+
+/**
+ * A FieldBrige able to convert the index representation back into an object without losing information
+ *
+ * Any bridge expected to process a document id should implement this interface
+ * EXPERIMENTAL Consider this interface as private
+ *
+ * @author Emmanuel Bernard
+ */
+//FIXME rework the interface inheritance there are some common concepts with StringBridge
+public interface TwoWayFieldBridge extends FieldBridge {
+	/**
+	 * build the element object from the Document
+	 *
+	 * The return value is the Entity id
+	 *
+	 * @param name	 field name
+	 * @param document document
+	 */
+	Object get(String name, Document document);
+
+	/**
+	 * convert the object representation to a String
+	 * The return String must not be null, it can be empty though
+	 * EXPERIMENTAL API subject to change in the future
+	 */
+	String objectToString(Object object);
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/TwoWayString2FieldBridgeAdaptor.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/TwoWayString2FieldBridgeAdaptor.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/TwoWayString2FieldBridgeAdaptor.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,30 @@
+//$Id: $
+package org.hibernate.search.bridge;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+
+/**
+ * Bridge to use a TwoWayStringBridge as a TwoWayFieldBridge
+ *
+ * @author Emmanuel Bernard
+ */
+//TODO use Generics to avoid double declaration of stringBridge 
+public class TwoWayString2FieldBridgeAdaptor extends String2FieldBridgeAdaptor implements TwoWayFieldBridge {
+
+	private TwoWayStringBridge stringBridge;
+
+	public TwoWayString2FieldBridgeAdaptor(TwoWayStringBridge stringBridge) {
+		super( stringBridge );
+		this.stringBridge = stringBridge;
+	}
+
+	public String objectToString(Object object) {
+		return stringBridge.objectToString( object );
+	}
+
+	public Object get(String name, Document document) {
+		Field field = document.getField( name );
+		return stringBridge.stringToObject( field.stringValue() );
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/TwoWayStringBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/TwoWayStringBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/TwoWayStringBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,18 @@
+//$Id: $
+package org.hibernate.search.bridge;
+
+/**
+ * StringBridge allowing a translation from the String back to the Object
+ * objectToString( stringToObject( string ) ) and stringToObject( objectToString( object ) )
+ * should be "idempotent". More precisely,
+ *
+ * objectToString( stringToObject( string ) ).equals(string) for string not null
+ * stringToObject( objectToString( object ) ).equals(object) for object not null 
+ * @author Emmanuel Bernard
+ */
+public interface TwoWayStringBridge extends StringBridge {
+	/**
+	 * Convert the string representation to an object
+	 */
+	Object stringToObject(String stringValue);
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/BigDecimalBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/BigDecimalBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/BigDecimalBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,18 @@
+//$Id: $
+package org.hibernate.search.bridge.builtin;
+
+import java.math.BigDecimal;
+
+import org.hibernate.util.StringHelper;
+
+/**
+ * Map a BigDecimal element
+ *
+ * @author Emmanuel Bernard
+ */
+public class BigDecimalBridge extends NumberBridge {
+	public Object stringToObject(String stringValue) {
+		if ( StringHelper.isEmpty( stringValue ) ) return null;
+		return new BigDecimal( stringValue );
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/BigIntegerBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/BigIntegerBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/BigIntegerBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,18 @@
+//$Id: $
+package org.hibernate.search.bridge.builtin;
+
+import java.math.BigInteger;
+
+import org.hibernate.util.StringHelper;
+
+/**
+ * Map a BigInteger element
+ *
+ * @author Emmanuel Bernard
+ */
+public class BigIntegerBridge extends NumberBridge {
+	public Object stringToObject(String stringValue) {
+		if ( StringHelper.isEmpty( stringValue ) ) return null;
+		return new BigInteger( stringValue );
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/DateBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/DateBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/DateBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,111 @@
+//$Id: $
+package org.hibernate.search.bridge.builtin;
+
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.lucene.document.DateTools;
+import org.hibernate.AssertionFailure;
+import org.hibernate.HibernateException;
+import org.hibernate.search.bridge.StringBridge;
+import org.hibernate.search.bridge.ParameterizedBridge;
+import org.hibernate.search.bridge.String2FieldBridgeAdaptor;
+import org.hibernate.search.annotations.Resolution;
+import org.hibernate.search.bridge.TwoWayString2FieldBridgeAdaptor;
+import org.hibernate.search.bridge.TwoWayStringBridge;
+import org.hibernate.util.StringHelper;
+
+/**
+ * Bridge a java.util.Date to a String, truncated to the resolution
+ * Date are stored GMT based
+ * <p/>
+ * ie
+ * Resolution.YEAR: yyyy
+ * Resolution.MONTH: yyyyMM
+ * Resolution.DAY: yyyyMMdd
+ * Resolution.HOUR: yyyyMMddHH
+ * Resolution.MINUTE: yyyyMMddHHmm
+ * Resolution.SECOND: yyyyMMddHHmmss
+ * Resolution.MILLISECOND: yyyyMMddHHmmssSSS
+ *
+ * @author Emmanuel Bernard
+ */
+//TODO split into StringBridge and TwoWayStringBridge?
+public class DateBridge implements TwoWayStringBridge, ParameterizedBridge {
+
+	public static final TwoWayStringBridge DATE_YEAR = new DateBridge( Resolution.YEAR );
+	public static final TwoWayStringBridge DATE_MONTH = new DateBridge( Resolution.MONTH );
+	public static final TwoWayStringBridge DATE_DAY = new DateBridge( Resolution.DAY );
+	public static final TwoWayStringBridge DATE_HOUR = new DateBridge( Resolution.HOUR );
+	public static final TwoWayStringBridge DATE_MINUTE = new DateBridge( Resolution.MINUTE );
+	public static final TwoWayStringBridge DATE_SECOND = new DateBridge( Resolution.SECOND );
+	public static final TwoWayStringBridge DATE_MILLISECOND = new DateBridge( Resolution.MILLISECOND );
+
+	DateTools.Resolution resolution;
+
+	public DateBridge() {
+	}
+
+	public DateBridge(Resolution resolution) {
+		setResolution( resolution );
+	}
+
+	public Object stringToObject(String stringValue) {
+		if ( StringHelper.isEmpty( stringValue ) ) return null;
+		try {
+			return DateTools.stringToDate( stringValue );
+		}
+		catch (ParseException e) {
+			throw new HibernateException( "Unable to parse into date: " + stringValue, e );
+		}
+	}
+
+	public String objectToString(Object object) {
+		return object != null ?
+				DateTools.dateToString( (Date) object, resolution ) :
+				null;
+	}
+
+	public void setParameterValues(Map parameters) {
+		Object resolution = parameters.get( "resolution" );
+		Resolution hibResolution;
+		if ( resolution instanceof String ) {
+			hibResolution = Resolution.valueOf( ( (String) resolution ).toUpperCase( Locale.ENGLISH ) );
+		}
+		else {
+			hibResolution = (Resolution) resolution;
+		}
+		setResolution( hibResolution );
+	}
+
+	private void setResolution(Resolution hibResolution) {
+		switch (hibResolution) {
+			case YEAR:
+				this.resolution = DateTools.Resolution.YEAR;
+				break;
+			case MONTH:
+				this.resolution = DateTools.Resolution.MONTH;
+				break;
+			case DAY:
+				this.resolution = DateTools.Resolution.DAY;
+				break;
+			case HOUR:
+				this.resolution = DateTools.Resolution.HOUR;
+				break;
+			case MINUTE:
+				this.resolution = DateTools.Resolution.MINUTE;
+				break;
+			case SECOND:
+				this.resolution = DateTools.Resolution.SECOND;
+				break;
+			case MILLISECOND:
+				this.resolution = DateTools.Resolution.MILLISECOND;
+				break;
+			default:
+				throw new AssertionFailure( "Unknown Resolution: " + hibResolution );
+
+		}
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/DoubleBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/DoubleBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/DoubleBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,16 @@
+//$Id: $
+package org.hibernate.search.bridge.builtin;
+
+import org.hibernate.util.StringHelper;
+
+/**
+ * Map a double element
+ *
+ * @author Emmanuel Bernard
+ */
+public class DoubleBridge extends NumberBridge {
+	public Object stringToObject(String stringValue) {
+		if ( StringHelper.isEmpty( stringValue ) ) return null;
+		return new Double( stringValue );
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/FloatBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/FloatBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/FloatBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,16 @@
+//$Id: $
+package org.hibernate.search.bridge.builtin;
+
+import org.hibernate.util.StringHelper;
+
+/**
+ * Map a float element
+ *
+ * @author Emmanuel Bernard
+ */
+public class FloatBridge extends NumberBridge {
+	public Object stringToObject(String stringValue) {
+		if ( StringHelper.isEmpty( stringValue ) ) return null;
+		return new Float( stringValue );
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/IntegerBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/IntegerBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/IntegerBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,16 @@
+//$Id: $
+package org.hibernate.search.bridge.builtin;
+
+import org.hibernate.util.StringHelper;
+
+/**
+ * Map an integer element
+ *
+ * @author Emmanuel Bernard
+ */
+public class IntegerBridge extends NumberBridge {
+	public Object stringToObject(String stringValue) {
+		if ( StringHelper.isEmpty( stringValue ) ) return null;
+		return new Integer( stringValue );
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/LongBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/LongBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/LongBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,16 @@
+//$Id: $
+package org.hibernate.search.bridge.builtin;
+
+import org.hibernate.util.StringHelper;
+
+/**
+ * Map a long element
+ *
+ * @author Emmanuel Bernard
+ */
+public class LongBridge extends NumberBridge {
+	public Object stringToObject(String stringValue) {
+		if ( StringHelper.isEmpty( stringValue ) ) return null;
+		return new Long( stringValue );
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/NumberBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/NumberBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/NumberBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,16 @@
+//$Id: $
+package org.hibernate.search.bridge.builtin;
+
+import org.hibernate.search.bridge.StringBridge;
+import org.hibernate.search.bridge.TwoWayStringBridge;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public abstract class NumberBridge implements TwoWayStringBridge {
+	public String objectToString(Object object) {
+		return object != null ?
+				object.toString() :
+				null;
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/ShortBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/ShortBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/ShortBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,16 @@
+//$Id: $
+package org.hibernate.search.bridge.builtin;
+
+import org.hibernate.util.StringHelper;
+
+/**
+ * Map a short element
+ *
+ * @author Emmanuel Bernard
+ */
+public class ShortBridge extends NumberBridge {
+	public Object stringToObject(String stringValue) {
+		if ( StringHelper.isEmpty( stringValue ) ) return null;
+		return new Short( stringValue );
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/StringBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/StringBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/bridge/builtin/StringBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,18 @@
+//$Id: $
+package org.hibernate.search.bridge.builtin;
+
+
+/**
+ * Map a string element
+ *
+ * @author Emmanuel Bernard
+ */
+public class StringBridge implements org.hibernate.search.bridge.TwoWayStringBridge {
+	public Object stringToObject(String stringValue) {
+		return stringValue;
+	}
+
+	public String objectToString(Object object) {
+		return (String) object;
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/engine/DocumentBuilder.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/engine/DocumentBuilder.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/engine/DocumentBuilder.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,329 @@
+//$Id: DocumentBuilder.java 10865 2006-11-23 23:30:01 +0100 (jeu., 23 nov. 2006) epbernard $
+package org.hibernate.search.engine;
+
+import java.io.Serializable;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+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.hibernate.AssertionFailure;
+import org.hibernate.HibernateException;
+import org.hibernate.cfg.annotations.Version;
+import org.hibernate.search.annotations.Boost;
+import org.hibernate.search.annotations.DocumentId;
+import org.hibernate.search.annotations.Index;
+import org.hibernate.search.annotations.Keyword;
+import org.hibernate.search.annotations.Store;
+import org.hibernate.search.annotations.Text;
+import org.hibernate.search.annotations.Unstored;
+import org.hibernate.search.bridge.BridgeFactory;
+import org.hibernate.search.bridge.FieldBridge;
+import org.hibernate.search.bridge.TwoWayFieldBridge;
+import org.hibernate.search.event.FullTextIndexEventListener;
+import org.hibernate.search.store.DirectoryProvider;
+import org.hibernate.search.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.XProperty;
+import org.hibernate.util.ReflectHelper;
+
+/**
+ * Set up and provide a manager for indexes classes
+ *
+ * @author Gavin King
+ * @author Emmanuel Bernard
+ * @author Sylvain Vieujot
+ * @author Richard Hallier
+ */
+public class DocumentBuilder<T> {
+
+	static {
+		Version.touch(); //touch version
+	}
+
+	private final List<XMember> keywordGetters = new ArrayList<XMember>();
+	private final List<String> keywordNames = new ArrayList<String>();
+	private final List<FieldBridge> keywordBridges = new ArrayList<FieldBridge>();
+	private final List<XMember> unstoredGetters = new ArrayList<XMember>();
+	private final List<String> unstoredNames = new ArrayList<String>();
+	private final List<FieldBridge> unstoredBridges = new ArrayList<FieldBridge>();
+	private final List<XMember> textGetters = new ArrayList<XMember>();
+	private final List<String> textNames = new ArrayList<String>();
+	private final List<FieldBridge> textBridges = new ArrayList<FieldBridge>();
+	private final List<String> fieldNames = new ArrayList<String>();
+	private final List<XMember> fieldGetters = new ArrayList<XMember>();
+	private final List<FieldBridge> fieldBridges = new ArrayList<FieldBridge>();
+	private final List<Field.Store> fieldStore = new ArrayList<Field.Store>();
+	private final List<Field.Index> fieldIndex = new ArrayList<Field.Index>();
+
+	private final XClass beanClass;
+	private final DirectoryProvider directoryProvider;
+	private String idKeywordName;
+	private final Analyzer analyzer;
+	private Float idBoost;
+	public static final String CLASS_FIELDNAME = "_hibernate_class";
+	private TwoWayFieldBridge idBridge;
+	private Set<Class> mappedSubclasses = new HashSet<Class>();
+	private ReflectionManager reflectionManager;
+
+
+	public DocumentBuilder(XClass clazz, Analyzer analyzer, DirectoryProvider directory,
+						   ReflectionManager reflectionManager) {
+		this.beanClass = clazz;
+		this.analyzer = analyzer;
+		this.directoryProvider = directory;
+		//FIXME get rid of it when boost is stored?
+		this.reflectionManager = reflectionManager;
+
+		if ( clazz == null ) throw new AssertionFailure( "Unable to build a DocumentBuilder with a null class" );
+
+		for ( XClass currClass = beanClass; currClass != null; currClass = currClass.getSuperclass() ) {
+			//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 ) {
+				initializeMember( method );
+			}
+
+			List<XProperty> fields = currClass.getDeclaredProperties( XClass.ACCESS_FIELD );
+			for ( XProperty field : fields ) {
+				initializeMember( field );
+			}
+		}
+
+		if ( idKeywordName == null ) {
+			throw new HibernateException( "No document id for: " + clazz.getName() );
+		}
+	}
+
+	private void initializeMember(XProperty 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 );
+				FieldBridge fieldBridge = BridgeFactory.guessType( member );
+				if ( fieldBridge instanceof TwoWayFieldBridge ) {
+					idBridge = (TwoWayFieldBridge) fieldBridge;
+				}
+				else {
+					throw new HibernateException(
+							"Bridge for document id does not implement IdFieldBridge: " + member.getName() );
+				}
+			}
+			else {
+				setAccessible( member );
+				keywordGetters.add( member );
+				keywordNames.add( name );
+				keywordBridges.add( BridgeFactory.guessType( member ) );
+			}
+		}
+
+		Unstored unstoredAnn = member.getAnnotation( Unstored.class );
+		if ( unstoredAnn != null ) {
+			setAccessible( member );
+			unstoredGetters.add( member );
+			unstoredNames.add( BinderHelper.getAttributeName( member, unstoredAnn.name() ) );
+			unstoredBridges.add( BridgeFactory.guessType( member ) );
+		}
+
+		Text textAnn = member.getAnnotation( Text.class );
+		if ( textAnn != null ) {
+			setAccessible( member );
+			textGetters.add( member );
+			textNames.add( BinderHelper.getAttributeName( member, textAnn.name() ) );
+			textBridges.add( BridgeFactory.guessType( member ) );
+		}
+
+		DocumentId documentIdAnn = member.getAnnotation( DocumentId.class );
+		if ( documentIdAnn != null ) {
+			if ( idKeywordName != null ) {
+				throw new AssertionFailure( "Two document id assigned: "
+						+ idKeywordName + " and " + BinderHelper.getAttributeName( member, documentIdAnn.name() ) );
+			}
+			idKeywordName = BinderHelper.getAttributeName( member, documentIdAnn.name() );
+			FieldBridge fieldBridge = BridgeFactory.guessType( member );
+			if ( fieldBridge instanceof TwoWayFieldBridge ) {
+				idBridge = (TwoWayFieldBridge) fieldBridge;
+			}
+			else {
+				throw new HibernateException(
+						"Bridge for document id does not implement IdFieldBridge: " + member.getName() );
+			}
+			idBoost = getBoost( member );
+		}
+
+		org.hibernate.search.annotations.Field fieldAnn =
+				member.getAnnotation( org.hibernate.search.annotations.Field.class );
+		if ( fieldAnn != null ) {
+			setAccessible( member );
+			fieldGetters.add( member );
+			fieldNames.add( BinderHelper.getAttributeName( member, fieldAnn.name() ) );
+			fieldStore.add( getStore( fieldAnn.store() ) );
+			fieldIndex.add( getIndex( fieldAnn.index() ) );
+			fieldBridges.add( BridgeFactory.guessType( member ) );
+		}
+	}
+
+	private Field.Store getStore(Store store) {
+		switch (store) {
+			case NO:
+				return Field.Store.NO;
+			case YES:
+				return Field.Store.YES;
+			case COMPRESS:
+				return Field.Store.COMPRESS;
+			default:
+				throw new AssertionFailure( "Unexpected Store: " + store );
+		}
+	}
+
+	private Field.Index getIndex(Index index) {
+		switch (index) {
+			case NO:
+				return Field.Index.NO;
+			case NO_NORMS:
+				return Field.Index.NO_NORMS;
+			case TOKENIZED:
+				return Field.Index.TOKENIZED;
+			case UN_TOKENISED:
+				return Field.Index.UN_TOKENIZED;
+			default:
+				throw new AssertionFailure( "Unexpected Index: " + index );
+		}
+	}
+
+	private Float getBoost(XAnnotatedElement element) {
+		if ( element == null ) return null;
+		Boost boost = element.getAnnotation( Boost.class );
+		return boost != null ?
+				boost.value() :
+				null;
+	}
+
+	private Object getMemberValue(T bean, XMember getter) {
+		Object value;
+		try {
+			value = getter.invoke( bean );
+		}
+		catch (Exception e) {
+			throw new IllegalStateException( "Could not get property value", e );
+		}
+		return value;
+	}
+
+	public Document getDocument(T instance, Serializable id) {
+		Document doc = new Document();
+		XClass instanceClass = reflectionManager.toXClass( instance.getClass() );
+		Float boost = getBoost( instanceClass );
+		if ( boost != null ) {
+			doc.setBoost( boost );
+		}
+		{
+			Field classField =
+					new Field( CLASS_FIELDNAME, instanceClass.getName(), Field.Store.YES, Field.Index.UN_TOKENIZED );
+			doc.add( classField );
+			idBridge.set( idKeywordName, id, doc, Field.Store.YES, Field.Index.UN_TOKENIZED, idBoost );
+		}
+		for ( int i = 0; i < keywordNames.size(); i++ ) {
+			XMember member = keywordGetters.get( i );
+			Object value = getMemberValue( instance, member );
+			keywordBridges.get( i ).set(
+					keywordNames.get( i ), value, doc, Field.Store.YES,
+					Field.Index.UN_TOKENIZED, getBoost( member )
+			);
+		}
+		for ( int i = 0; i < textNames.size(); i++ ) {
+			XMember member = textGetters.get( i );
+			Object value = getMemberValue( instance, member );
+			textBridges.get( i ).set(
+					textNames.get( i ), value, doc, Field.Store.YES,
+					Field.Index.TOKENIZED, getBoost( member )
+			);
+		}
+		for ( int i = 0; i < unstoredNames.size(); i++ ) {
+			XMember member = unstoredGetters.get( i );
+			Object value = getMemberValue( instance, member );
+			unstoredBridges.get( i ).set(
+					unstoredNames.get( i ), value, doc, Field.Store.NO,
+					Field.Index.TOKENIZED, getBoost( member )
+			);
+		}
+		for ( int i = 0; i < fieldNames.size(); i++ ) {
+			XMember member = fieldGetters.get( i );
+			Object value = getMemberValue( instance, member );
+			fieldBridges.get( i ).set(
+					fieldNames.get( i ), value, doc, fieldStore.get( i ),
+					fieldIndex.get( i ), getBoost( member )
+			);
+		}
+		return doc;
+	}
+
+	public Term getTerm(Serializable id) {
+		return new Term( idKeywordName, idBridge.objectToString( id ) );
+	}
+
+	public DirectoryProvider getDirectoryProvider() {
+		return directoryProvider;
+	}
+
+	public Analyzer getAnalyzer() {
+		return analyzer;
+	}
+
+	private static void setAccessible(XMember member) {
+		if ( !Modifier.isPublic( member.getModifiers() ) ) {
+			member.setAccessible( true );
+		}
+	}
+
+	public TwoWayFieldBridge getIdBridge() {
+		return idBridge;
+	}
+
+	public String getIdKeywordName() {
+		return idKeywordName;
+	}
+
+	public static Class getDocumentClass(Document document) {
+		String className = document.get( DocumentBuilder.CLASS_FIELDNAME );
+		try {
+			return ReflectHelper.classForName( className );
+		}
+		catch (ClassNotFoundException e) {
+			throw new HibernateException( "Unable to load indexed class: " + className, e );
+		}
+	}
+
+	public static Serializable getDocumentId(FullTextIndexEventListener listener, Class clazz, Document document) {
+		DocumentBuilder builder = listener.getDocumentBuilders().get( clazz );
+		if ( builder == null ) throw new HibernateException( "No Lucene configuration set up for: " + clazz.getName() );
+		return (Serializable) builder.getIdBridge().get( builder.getIdKeywordName(), document );
+	}
+
+	public void postInitialize(Set<Class> indexedClasses) {
+		//this method does not requires synchronization
+		Class plainClass = reflectionManager.toClass( beanClass );
+		Set<Class> tempMappedSubclasses = new HashSet<Class>();
+		//together with the caller this creates a o(2), but I think it's still faster than create the up hierarchy for each class
+		for ( Class currentClass : indexedClasses ) {
+			if ( plainClass.isAssignableFrom( currentClass ) ) tempMappedSubclasses.add( currentClass );
+		}
+		mappedSubclasses = Collections.unmodifiableSet( tempMappedSubclasses );
+	}
+
+
+	public Set<Class> getMappedSubclasses() {
+		return mappedSubclasses;
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/event/FullTextIndexEventListener.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/event/FullTextIndexEventListener.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/event/FullTextIndexEventListener.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,180 @@
+//$Id: FullTextIndexEventListener.java 10865 2006-11-23 23:30:01 +0100 (jeu., 23 nov. 2006) epbernard $
+package org.hibernate.search.event;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+import javax.transaction.Status;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.hibernate.HibernateException;
+import org.hibernate.cfg.AnnotationConfiguration;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.event.AbstractEvent;
+import org.hibernate.event.Initializable;
+import org.hibernate.event.PostDeleteEvent;
+import org.hibernate.event.PostDeleteEventListener;
+import org.hibernate.event.PostInsertEvent;
+import org.hibernate.event.PostInsertEventListener;
+import org.hibernate.event.PostUpdateEvent;
+import org.hibernate.event.PostUpdateEventListener;
+import org.hibernate.search.Environment;
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.backend.AddWork;
+import org.hibernate.search.backend.DeleteWork;
+import org.hibernate.search.backend.UpdateWork;
+import org.hibernate.search.backend.Work;
+import org.hibernate.search.backend.WorkQueue;
+import org.hibernate.search.backend.impl.BatchLuceneWorkQueue;
+import org.hibernate.search.backend.impl.PostTransactionWorkQueueSynchronization;
+import org.hibernate.search.engine.DocumentBuilder;
+import org.hibernate.search.store.DirectoryProvider;
+import org.hibernate.search.store.DirectoryProviderFactory;
+import org.hibernate.mapping.PersistentClass;
+import org.hibernate.reflection.ReflectionManager;
+import org.hibernate.reflection.XClass;
+import org.hibernate.util.ReflectHelper;
+
+/**
+ * This listener supports setting a parent directory for all generated index files.
+ * It also supports setting the analyzer class to be used.
+ *
+ * @author Gavin King
+ * @author Emmanuel Bernard
+ * @author Mattias Arbin
+ */
+//TODO work on sharing the same indexWriters and readers across a single post operation...
+//TODO implement and use a LockableDirectoryProvider that wraps a DP to handle the lock inside the LDP
+public class FullTextIndexEventListener implements PostDeleteEventListener, PostInsertEventListener,
+		PostUpdateEventListener, Initializable {
+	ReflectionManager reflectionManager;
+
+	//FIXME keeping this here is a bad decision since you might want to search indexes wo maintain it
+	@Deprecated
+	public Map<Class, DocumentBuilder<Object>> getDocumentBuilders() {
+		return documentBuilders;
+	}
+
+
+	private Map<Class, DocumentBuilder<Object>> documentBuilders = new HashMap<Class, DocumentBuilder<Object>>();
+	//keep track of the index modifiers per DirectoryProvider since multiple entity can use the same directory provider
+	private Map<DirectoryProvider, ReentrantLock> lockableDirectoryProviders =
+			new HashMap<DirectoryProvider, ReentrantLock>();
+	private boolean initialized;
+
+	private static final Log log = LogFactory.getLog( FullTextIndexEventListener.class );
+
+	public void initialize(Configuration cfg) {
+		if ( initialized ) return;
+		//yuk
+		reflectionManager = ( (AnnotationConfiguration) cfg ).createExtendedMappings().getReflectionManager();
+
+		Class analyzerClass;
+		String analyzerClassName = cfg.getProperty( Environment.ANALYZER_CLASS );
+		if ( analyzerClassName != null ) {
+			try {
+				analyzerClass = ReflectHelper.classForName( analyzerClassName );
+			}
+			catch (Exception e) {
+				throw new HibernateException(
+						"Lucene analyzer class '" + analyzerClassName + "' defined in property '" + Environment.ANALYZER_CLASS + "' could not be found.",
+						e
+				);
+			}
+		}
+		else {
+			analyzerClass = StandardAnalyzer.class;
+		}
+		// Initialize analyzer
+		Analyzer analyzer;
+		try {
+			analyzer = (Analyzer) analyzerClass.newInstance();
+		}
+		catch (ClassCastException e) {
+			throw new HibernateException(
+					"Lucene analyzer does not implement " + Analyzer.class.getName() + ": " + analyzerClassName
+			);
+		}
+		catch (Exception e) {
+			throw new HibernateException( "Failed to instantiate lucene analyzer with type " + analyzerClassName );
+		}
+
+		Iterator iter = cfg.getClassMappings();
+		DirectoryProviderFactory factory = new DirectoryProviderFactory();
+		while ( iter.hasNext() ) {
+			PersistentClass clazz = (PersistentClass) iter.next();
+			Class<?> mappedClass = clazz.getMappedClass();
+			if ( mappedClass != null ) {
+				XClass mappedXClass = reflectionManager.toXClass( mappedClass );
+				if ( mappedXClass != null && mappedXClass.isAnnotationPresent( Indexed.class ) ) {
+					DirectoryProvider provider = factory.createDirectoryProvider( mappedXClass, cfg );
+					if ( !lockableDirectoryProviders.containsKey( provider ) ) {
+						lockableDirectoryProviders.put( provider, new ReentrantLock() );
+					}
+					final DocumentBuilder<Object> documentBuilder = new DocumentBuilder<Object>(
+							mappedXClass, analyzer, provider, reflectionManager
+					);
+
+					documentBuilders.put( mappedClass, documentBuilder );
+				}
+			}
+		}
+		Set<Class> indexedClasses = documentBuilders.keySet();
+		for ( DocumentBuilder builder : documentBuilders.values() ) {
+			builder.postInitialize( indexedClasses );
+		}
+		initialized = true;
+	}
+
+	public void onPostDelete(PostDeleteEvent event) {
+		if ( documentBuilders.containsKey( event.getEntity().getClass() ) ) {
+			DeleteWork work = new DeleteWork( event.getId(), event.getEntity().getClass() );
+			processWork( work, event );
+		}
+	}
+
+	public void onPostInsert(PostInsertEvent event) {
+		final Object entity = event.getEntity();
+		DocumentBuilder<Object> builder = documentBuilders.get( entity.getClass() );
+		if ( builder != null ) {
+			Serializable id = event.getId();
+			Document doc = builder.getDocument( entity, id );
+			AddWork work = new AddWork( id, entity.getClass(), doc );
+			processWork( work, event );
+		}
+	}
+
+	public void onPostUpdate(PostUpdateEvent event) {
+		final Object entity = event.getEntity();
+		DocumentBuilder<Object> builder = documentBuilders.get( entity.getClass() );
+		if ( builder != null ) {
+			Serializable id = event.getId();
+			Document doc = builder.getDocument( entity, id );
+			UpdateWork work = new UpdateWork( id, entity.getClass(), doc );
+			processWork( work, event );
+		}
+	}
+
+	private void processWork(Work work, AbstractEvent event) {
+		WorkQueue workQueue = new BatchLuceneWorkQueue( documentBuilders, lockableDirectoryProviders );
+		workQueue.add( work );
+		PostTransactionWorkQueueSynchronization sync = new PostTransactionWorkQueueSynchronization( workQueue );
+		if ( event.getSession().isTransactionInProgress() ) {
+			event.getSession().getTransaction().registerSynchronization( sync );
+		}
+		else {
+			sync.afterCompletion( Status.STATUS_COMMITTED );
+		}
+	}
+
+	public Map<DirectoryProvider, ReentrantLock> getLockableDirectoryProviders() {
+		return lockableDirectoryProviders;
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/impl/FullTextSessionImpl.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/impl/FullTextSessionImpl.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/impl/FullTextSessionImpl.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,466 @@
+//$Id: $
+package org.hibernate.search.impl;
+
+import java.io.Serializable;
+import java.sql.Connection;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.transaction.Status;
+
+import org.hibernate.CacheMode;
+import org.hibernate.Criteria;
+import org.hibernate.EntityMode;
+import org.hibernate.Filter;
+import org.hibernate.FlushMode;
+import org.hibernate.HibernateException;
+import org.hibernate.LockMode;
+import org.hibernate.Query;
+import org.hibernate.ReplicationMode;
+import org.hibernate.SQLQuery;
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.Transaction;
+import org.hibernate.engine.query.ParameterMetadata;
+import org.hibernate.impl.SessionImpl;
+import org.hibernate.search.query.FullTextQueryImpl;
+import org.hibernate.search.event.FullTextIndexEventListener;
+import org.hibernate.search.util.ContextHelper;
+import org.hibernate.search.engine.DocumentBuilder;
+import org.hibernate.search.backend.UpdateWork;
+import org.hibernate.search.backend.Work;
+import org.hibernate.search.backend.WorkQueue;
+import org.hibernate.search.backend.impl.BatchLuceneWorkQueue;
+import org.hibernate.search.backend.impl.PostTransactionWorkQueueSynchronization;
+import org.hibernate.search.store.DirectoryProvider;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.stat.SessionStatistics;
+import org.hibernate.type.Type;
+import org.apache.lucene.document.Document;
+
+/**
+ * Lucene Full text search aware session
+ *
+ * @author Emmanuel Bernard
+ */
+public class FullTextSessionImpl implements FullTextSession {
+	private final SessionImpl session;
+	private PostTransactionWorkQueueSynchronization postTransactionWorkQueueSynch;
+
+	public FullTextSessionImpl(Session session) {
+		this.session = (SessionImpl) session;
+	}
+
+	/**
+	 * Execute a Lucene query and retrieve managed objects of type entities (or their indexed subclasses)
+     * If entities is empty, include all indexed entities
+     * 
+	 * @param entities must be immutable for the lifetime of the query object
+	 */
+	public Query createFullTextQuery(org.apache.lucene.search.Query luceneQuery, Class... entities) {
+		return new FullTextQueryImpl( luceneQuery, entities, session, new ParameterMetadata(null, null) );
+	}
+
+	/**
+	 * (re)index an entity.
+	 * Non indexable entities are ignored
+	 * The entity must be associated with the session
+	 *
+	 * @param entity must not be null
+	 */
+	public void index(Object entity) {
+		if (entity == null) return;
+		Class clazz = entity.getClass();
+		FullTextIndexEventListener listener = ContextHelper.getLuceneEventListener( session );
+		DocumentBuilder<Object> builder = listener.getDocumentBuilders().get( clazz );
+		if ( builder != null ) {
+			Serializable id = session.getIdentifier( entity );
+			Document doc = builder.getDocument( entity, id );
+			UpdateWork work = new UpdateWork( id, entity.getClass(), doc );
+			processWork( work, listener.getDocumentBuilders(), listener.getLockableDirectoryProviders() );
+		}
+		//TODO
+		//need to add elements in a queue kept at the Session level
+		//the queue will be processed by a Lucene(Auto)FlushEventListener
+		//note that we could keep this queue somewhere in the event listener in the mean time but that requires
+		// a synchronized hashmap holding this queue on a per session basis plus some session house keeping (yuk)
+		//an other solution would be to subclass SessionImpl instead of having this LuceneSession delecation model
+		// this is an open discussion
+	}
+
+	private void processWork(Work work, Map<Class, DocumentBuilder<Object>> documentBuilders,
+							 Map<DirectoryProvider, ReentrantLock> lockableDirectoryProviders) {
+		if ( session.isTransactionInProgress() ) {
+			if ( postTransactionWorkQueueSynch == null || postTransactionWorkQueueSynch.isConsumed() ) {
+				postTransactionWorkQueueSynch = createWorkQueueSync( documentBuilders, lockableDirectoryProviders);
+				session.getTransaction().registerSynchronization( postTransactionWorkQueueSynch );
+			}
+			postTransactionWorkQueueSynch.add( work );
+		}
+		else {
+			//no transaction work right away
+			PostTransactionWorkQueueSynchronization sync =
+					createWorkQueueSync( documentBuilders, lockableDirectoryProviders );
+			sync.add( work );
+			sync.afterCompletion( Status.STATUS_COMMITTED );
+		}
+	}
+
+	private PostTransactionWorkQueueSynchronization createWorkQueueSync(
+			Map<Class, DocumentBuilder<Object>> documentBuilders,
+			Map<DirectoryProvider, ReentrantLock> lockableDirectoryProviders) {
+		WorkQueue workQueue = new BatchLuceneWorkQueue( documentBuilders, lockableDirectoryProviders );
+		return new PostTransactionWorkQueueSynchronization( workQueue );
+	}
+
+	public Query createSQLQuery(String sql, String returnAlias, Class returnClass) {
+		return session.createSQLQuery( sql, returnAlias, returnClass );
+	}
+
+	public Query createSQLQuery(String sql, String[] returnAliases, Class[] returnClasses) {
+		return session.createSQLQuery( sql, returnAliases, returnClasses );
+	}
+
+	public int delete(String query) throws HibernateException {
+		return session.delete( query );
+	}
+
+	public int delete(String query, Object value, Type type) throws HibernateException {
+		return session.delete( query, value, type );
+	}
+
+	public int delete(String query, Object[] values, Type[] types) throws HibernateException {
+		return session.delete( query, values, types );
+	}
+
+	public Collection filter(Object collection, String filter) throws HibernateException {
+		return session.filter( collection, filter );
+	}
+
+	public Collection filter(Object collection, String filter, Object value, Type type) throws HibernateException {
+		return session.filter( collection, filter, value, type );
+	}
+
+	public Collection filter(Object collection, String filter, Object[] values, Type[] types) throws HibernateException {
+		return session.filter( collection, filter, values, types );
+	}
+
+	public List find(String query) throws HibernateException {
+		return session.find( query );
+	}
+
+	public List find(String query, Object value, Type type) throws HibernateException {
+		return session.find( query, value, type );
+	}
+
+	public List find(String query, Object[] values, Type[] types) throws HibernateException {
+		return session.find( query, values, types );
+	}
+
+	public Iterator iterate(String query) throws HibernateException {
+		return session.iterate( query );
+	}
+
+	public Iterator iterate(String query, Object value, Type type) throws HibernateException {
+		return session.iterate( query, value, type );
+	}
+
+	public Iterator iterate(String query, Object[] values, Type[] types) throws HibernateException {
+		return session.iterate( query, values, types );
+	}
+
+	public void save(String entityName, Object object, Serializable id) throws HibernateException {
+		session.save( entityName, object, id );
+	}
+
+	public void save(Object object, Serializable id) throws HibernateException {
+		session.save( object, id );
+	}
+
+	public Object saveOrUpdateCopy(String entityName, Object object) throws HibernateException {
+		return session.saveOrUpdateCopy( entityName, object );
+	}
+
+	public Object saveOrUpdateCopy(String entityName, Object object, Serializable id) throws HibernateException {
+		return session.saveOrUpdateCopy( entityName, object, id );
+	}
+
+	public Object saveOrUpdateCopy(Object object) throws HibernateException {
+		return session.saveOrUpdateCopy( object );
+	}
+
+	public Object saveOrUpdateCopy(Object object, Serializable id) throws HibernateException {
+		return session.saveOrUpdateCopy( object, id );
+	}
+
+	public void update(String entityName, Object object, Serializable id) throws HibernateException {
+		session.update( entityName, object, id );
+	}
+
+	public void update(Object object, Serializable id) throws HibernateException {
+		session.update( object, id );
+	}
+
+	public Transaction beginTransaction() throws HibernateException {
+		return session.beginTransaction();
+	}
+
+	public void cancelQuery() throws HibernateException {
+		session.cancelQuery();
+	}
+
+	public void clear() {
+		session.clear();
+	}
+
+	public Connection close() throws HibernateException {
+		return session.close();
+	}
+
+	public Connection connection() throws HibernateException {
+		return session.connection();
+	}
+
+	public boolean contains(Object object) {
+		return session.contains( object );
+	}
+
+	public Criteria createCriteria(String entityName) {
+		return session.createCriteria( entityName );
+	}
+
+	public Criteria createCriteria(String entityName, String alias) {
+		return session.createCriteria( entityName, alias );
+	}
+
+	public Criteria createCriteria(Class persistentClass) {
+		return session.createCriteria( persistentClass );
+	}
+
+	public Criteria createCriteria(Class persistentClass, String alias) {
+		return session.createCriteria( persistentClass, alias );
+	}
+
+	public Query createFilter(Object collection, String queryString) throws HibernateException {
+		return session.createFilter( collection, queryString );
+	}
+
+	public Query createQuery(String queryString) throws HibernateException {
+		return session.createQuery( queryString );
+	}
+
+	public SQLQuery createSQLQuery(String queryString) throws HibernateException {
+		return session.createSQLQuery( queryString );
+	}
+
+	public void delete(String entityName, Object object) throws HibernateException {
+		session.delete( entityName, object );
+	}
+
+	public void delete(Object object) throws HibernateException {
+		session.delete( object );
+	}
+
+	public void disableFilter(String filterName) {
+		session.disableFilter( filterName );
+	}
+
+	public Connection disconnect() throws HibernateException {
+		return session.disconnect();
+	}
+
+	public Filter enableFilter(String filterName) {
+		return session.enableFilter( filterName );
+	}
+
+	public void evict(Object object) throws HibernateException {
+		session.evict( object );
+	}
+
+	public void flush() throws HibernateException {
+		session.flush();
+	}
+
+	public Object get(Class clazz, Serializable id) throws HibernateException {
+		return session.get( clazz, id );
+	}
+
+	public Object get(Class clazz, Serializable id, LockMode lockMode) throws HibernateException {
+		return session.get( clazz, id, lockMode );
+	}
+
+	public Object get(String entityName, Serializable id) throws HibernateException {
+		return session.get( entityName, id );
+	}
+
+	public Object get(String entityName, Serializable id, LockMode lockMode) throws HibernateException {
+		return session.get( entityName, id, lockMode );
+	}
+
+	public CacheMode getCacheMode() {
+		return session.getCacheMode();
+	}
+
+	public LockMode getCurrentLockMode(Object object) throws HibernateException {
+		return session.getCurrentLockMode( object );
+	}
+
+	public Filter getEnabledFilter(String filterName) {
+		return session.getEnabledFilter( filterName );
+	}
+
+	public EntityMode getEntityMode() {
+		return session.getEntityMode();
+	}
+
+	public String getEntityName(Object object) throws HibernateException {
+		return session.getEntityName( object );
+	}
+
+	public FlushMode getFlushMode() {
+		return session.getFlushMode();
+	}
+
+	public Serializable getIdentifier(Object object) throws HibernateException {
+		return session.getIdentifier( object );
+	}
+
+	public Query getNamedQuery(String queryName) throws HibernateException {
+		return session.getNamedQuery( queryName );
+	}
+
+	public org.hibernate.Session getSession(EntityMode entityMode) {
+		return session.getSession( entityMode );
+	}
+
+	public SessionFactory getSessionFactory() {
+		return session.getSessionFactory();
+	}
+
+	public SessionStatistics getStatistics() {
+		return session.getStatistics();
+	}
+
+	public Transaction getTransaction() {
+		return session.getTransaction();
+	}
+
+	public boolean isConnected() {
+		return session.isConnected();
+	}
+
+	public boolean isDirty() throws HibernateException {
+		return session.isDirty();
+	}
+
+	public boolean isOpen() {
+		return session.isOpen();
+	}
+
+	public Object load(String entityName, Serializable id) throws HibernateException {
+		return session.load( entityName, id );
+	}
+
+	public Object load(String entityName, Serializable id, LockMode lockMode) throws HibernateException {
+		return session.load( entityName, id, lockMode );
+	}
+
+	public void load(Object object, Serializable id) throws HibernateException {
+		session.load( object, id );
+	}
+
+	public Object load(Class theClass, Serializable id) throws HibernateException {
+		return session.load( theClass, id );
+	}
+
+	public Object load(Class theClass, Serializable id, LockMode lockMode) throws HibernateException {
+		return session.load( theClass, id, lockMode );
+	}
+
+	public void lock(String entityName, Object object, LockMode lockMode) throws HibernateException {
+		session.lock( entityName, object, lockMode );
+	}
+
+	public void lock(Object object, LockMode lockMode) throws HibernateException {
+		session.lock( object, lockMode );
+	}
+
+	public Object merge(String entityName, Object object) throws HibernateException {
+		return session.merge( entityName, object );
+	}
+
+	public Object merge(Object object) throws HibernateException {
+		return session.merge( object );
+	}
+
+	public void persist(String entityName, Object object) throws HibernateException {
+		session.persist( entityName, object );
+	}
+
+	public void persist(Object object) throws HibernateException {
+		session.persist( object );
+	}
+
+	public void reconnect() throws HibernateException {
+		session.reconnect();
+	}
+
+	public void reconnect(Connection connection) throws HibernateException {
+		session.reconnect( connection );
+	}
+
+	public void refresh(Object object) throws HibernateException {
+		session.refresh( object );
+	}
+
+	public void refresh(Object object, LockMode lockMode) throws HibernateException {
+		session.refresh( object, lockMode );
+	}
+
+	public void replicate(String entityName, Object object, ReplicationMode replicationMode) throws HibernateException {
+		session.replicate( entityName, object, replicationMode );
+	}
+
+	public void replicate(Object object, ReplicationMode replicationMode) throws HibernateException {
+		session.replicate( object, replicationMode );
+	}
+
+	public Serializable save(String entityName, Object object) throws HibernateException {
+		return session.save( entityName, object );
+	}
+
+	public Serializable save(Object object) throws HibernateException {
+		return session.save( object );
+	}
+
+	public void saveOrUpdate(String entityName, Object object) throws HibernateException {
+		session.saveOrUpdate( entityName, object );
+	}
+
+	public void saveOrUpdate(Object object) throws HibernateException {
+		session.saveOrUpdate( object );
+	}
+
+	public void setCacheMode(CacheMode cacheMode) {
+		session.setCacheMode( cacheMode );
+	}
+
+	public void setFlushMode(FlushMode flushMode) {
+		session.setFlushMode( flushMode );
+	}
+
+	public void setReadOnly(Object entity, boolean readOnly) {
+		session.setReadOnly( entity, readOnly );
+	}
+
+	public void update(String entityName, Object object) throws HibernateException {
+		session.update( entityName, object );
+	}
+
+	public void update(Object object) throws HibernateException {
+		session.update( object );
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/EntityInfo.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/EntityInfo.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/EntityInfo.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,12 @@
+//$Id: $
+package org.hibernate.search.query;
+
+import java.io.Serializable;
+
+/**
+ * @author Emmanuel Bernard
+ */
+class EntityInfo {
+	public Class clazz;
+	public Serializable id;
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/FullTextQueryImpl.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/FullTextQueryImpl.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/FullTextQueryImpl.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,296 @@
+//$Id: $
+package org.hibernate.search.query;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Hits;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.MultiSearcher;
+import org.apache.lucene.search.Searcher;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.store.Directory;
+import org.hibernate.Hibernate;
+import org.hibernate.HibernateException;
+import org.hibernate.LockMode;
+import org.hibernate.Query;
+import org.hibernate.ScrollMode;
+import org.hibernate.ScrollableResults;
+import org.hibernate.Session;
+import org.hibernate.engine.SessionImplementor;
+import org.hibernate.engine.query.ParameterMetadata;
+import org.hibernate.impl.AbstractQueryImpl;
+import org.hibernate.search.engine.DocumentBuilder;
+import org.hibernate.search.event.FullTextIndexEventListener;
+import org.hibernate.search.util.ContextHelper;
+
+/**
+ * @author Emmanuel Bernard
+ */
+//TODO implements setParameter()
+public class FullTextQueryImpl extends AbstractQueryImpl {
+	private static final Log log = LogFactory.getLog( FullTextQueryImpl.class );
+	private org.apache.lucene.search.Query luceneQuery;
+	private Class[] classes;
+	private Set<Class> classesAndSubclasses;
+	private Integer firstResult;
+	private Integer maxResults;
+	private int resultSize;
+
+	/**
+	 * classes must be immutable
+	 */
+	public FullTextQueryImpl(org.apache.lucene.search.Query query, Class[] classes, SessionImplementor session,
+						   ParameterMetadata parameterMetadata) {
+		//TODO handle flushMode
+		super( query.toString(), null, session, parameterMetadata );
+		this.luceneQuery = query;
+		this.classes = classes;
+	}
+
+	/**
+	 * Return an interator on the results.
+	 * Retrieve the object one by one (initialize it during the next() operation)
+	 */
+	public Iterator iterate() throws HibernateException {
+		//implement an interator which keep the id/class for each hit and get the object on demand
+		//cause I can't keep the searcher and hence the hit opened. I dont have any hook to know when the
+		//user stop using it
+		//scrollable is better in this area
+
+		FullTextIndexEventListener listener = ContextHelper.getLuceneEventListener( session );
+		//find the directories
+		Searcher searcher = buildSearcher( listener );
+		try {
+			org.apache.lucene.search.Query query = filterQueryByClasses( luceneQuery );
+			Hits hits = searcher.search( query );
+			setResultSize( hits );
+			int first = first();
+			int max = max( first, hits );
+			List<EntityInfo> entityInfos = new ArrayList<EntityInfo>( max - first + 1 );
+			for ( int index = first; index <= max; index++ ) {
+				Document document = hits.doc( index );
+				EntityInfo entityInfo = new EntityInfo();
+				entityInfo.clazz = DocumentBuilder.getDocumentClass( document );
+				entityInfo.id = DocumentBuilder.getDocumentId( listener, entityInfo.clazz, document );
+				entityInfos.add( entityInfo );
+			}
+			return new IteratorImpl( entityInfos, (Session) this.session );
+		}
+		catch (IOException e) {
+			throw new HibernateException( "Unable to query Lucene index", e );
+		}
+		finally {
+			if ( searcher != null ) try {
+				searcher.close();
+			}
+			catch (IOException e) {
+				log.warn( "Unable to properly close searcher during lucene query: " + getQueryString(), e );
+			}
+		}
+	}
+
+		public ScrollableResults scroll() throws HibernateException {
+		//keep the searcher open until the resultset is closed
+		FullTextIndexEventListener listener = ContextHelper.getLuceneEventListener( session );;
+		//find the directories
+		Searcher searcher = buildSearcher( listener );
+		Hits hits;
+		try {
+			org.apache.lucene.search.Query query = filterQueryByClasses( luceneQuery );
+			hits = searcher.search( query );
+			setResultSize( hits );
+			int first = first();
+			int max = max( first, hits );
+			return new ScrollableResultsImpl( searcher, hits, first, max, (Session) this.session, listener );
+		}
+		catch (IOException e) {
+			try {
+				if ( searcher != null ) searcher.close();
+			}
+			catch (IOException ee) {
+				//we have the initial issue already
+			}
+			throw new HibernateException( "Unable to query Lucene index", e );
+		}
+	}
+
+	public ScrollableResults scroll(ScrollMode scrollMode) throws HibernateException {
+		//TODO think about this scrollmode
+		return scroll();
+	}
+
+	public List list() throws HibernateException {
+		FullTextIndexEventListener listener = ContextHelper.getLuceneEventListener( session );;
+		//find the directories
+		Searcher searcher = buildSearcher( listener );
+		Hits hits;
+		try {
+			org.apache.lucene.search.Query query = filterQueryByClasses( luceneQuery );
+			hits = searcher.search( query );
+			setResultSize( hits );
+			int first = first();
+			int max = max( first, hits );
+			List result = new ArrayList( max - first + 1 );
+			Session sess = (Session) this.session;
+			for ( int index = first; index <= max; index++ ) {
+				Document document = hits.doc( index );
+				Class clazz = DocumentBuilder.getDocumentClass( document );
+				Serializable id = DocumentBuilder.getDocumentId( listener, clazz, document );
+				result.add( sess.load( clazz, id ) );
+				//use load to benefit from the batch-size
+				//we don't face proxy casting issues since the exact class is extracted from the index
+			}
+			//then initialize the objects
+			for ( Object element : result ) {
+				Hibernate.initialize( element );
+			}
+			return result;
+		}
+		catch (IOException e) {
+			throw new HibernateException( "Unable to query Lucene index", e );
+		}
+		finally {
+			if ( searcher != null ) try {
+				searcher.close();
+			}
+			catch (IOException e) {
+				log.warn( "Unable to properly close searcher during lucene query: " + getQueryString(), e );
+			}
+		}
+	}
+
+	private org.apache.lucene.search.Query filterQueryByClasses(org.apache.lucene.search.Query luceneQuery) {
+		//A query filter is more practical than a manual class filtering post query (esp on scrollable resultsets)
+		//it also probably minimise the memory footprint
+		if ( classesAndSubclasses == null ) {
+			return luceneQuery;
+		}
+		else {
+			BooleanQuery classFilter = new BooleanQuery();
+			//annihilate the scoring impact of DocumentBuilder.CLASS_FIELDNAME
+			classFilter.setBoost( 0 );
+			for ( Class clazz : classesAndSubclasses ) {
+				Term t = new Term( DocumentBuilder.CLASS_FIELDNAME, clazz.getName() );
+				TermQuery termQuery = new TermQuery( t );
+				classFilter.add( termQuery, BooleanClause.Occur.SHOULD );
+			}
+			BooleanQuery filteredQuery = new BooleanQuery();
+			filteredQuery.add( luceneQuery, BooleanClause.Occur.MUST );
+			filteredQuery.add( classFilter, BooleanClause.Occur.MUST );
+			return filteredQuery;
+		}
+	}
+
+	private int max(int first, Hits hits) {
+		return maxResults == null ?
+				hits.length() - 1 :
+				maxResults + first < hits.length() ?
+						first + maxResults - 1 :
+						hits.length() - 1;
+	}
+
+	private int first() {
+		return firstResult != null ?
+				firstResult :
+				0;
+	}
+
+	//TODO change classesAndSubclasses by side effect, which is a mismatch with the Searcher return, fix that.
+	private Searcher buildSearcher(FullTextIndexEventListener listener) {
+		Map<Class, DocumentBuilder<Object>> builders = listener.getDocumentBuilders();
+		Set<Directory> directories = new HashSet<Directory>();
+		if ( classes == null || classes.length == 0 ) {
+			//no class means all classes
+			for ( DocumentBuilder builder : builders.values() ) {
+				directories.add( builder.getDirectoryProvider().getDirectory() );
+			}
+			classesAndSubclasses = null;
+		}
+		else {
+			Set<Class> involvedClasses = new HashSet<Class>( classes.length );
+			Collections.addAll( involvedClasses, classes );
+			for ( Class clazz : classes ) {
+				DocumentBuilder builder = builders.get( clazz );
+				if ( builder != null ) involvedClasses.addAll( builder.getMappedSubclasses() );
+			}
+			for ( Class clazz : involvedClasses ) {
+				DocumentBuilder builder = builders.get( clazz );
+				//TODO should we rather choose a polymorphic path and allow non mapped entities
+				if ( builder == null ) throw new HibernateException( "Not a mapped entity: " + clazz );
+				directories.add( builder.getDirectoryProvider().getDirectory() );
+			}
+			classesAndSubclasses = involvedClasses;
+		}
+
+		//set up the searcher
+		Searcher searcher;
+		int dirNbr = directories.size();
+		if ( dirNbr > 1 ) {
+			try {
+				IndexSearcher[] searchers = new IndexSearcher[dirNbr];
+				Iterator<Directory> it = directories.iterator();
+				for ( int index = 0; index < dirNbr; index++ ) {
+					searchers[index] = new IndexSearcher( it.next() );
+				}
+				searcher = new MultiSearcher( searchers );
+			}
+			catch (IOException e) {
+				throw new HibernateException( "Unable to read Lucene directory", e );
+			}
+		}
+		else {
+			try {
+				searcher = new IndexSearcher( directories.iterator().next() );
+			}
+			catch (IOException e) {
+				throw new HibernateException( "Unable to read Lucene directory", e );
+			}
+		}
+		return searcher;
+	}
+
+	private void setResultSize(Hits hits) {
+		resultSize = hits.length();
+	}
+
+	//FIXME does it make sense
+	public int resultSize() {
+		return this.resultSize;
+	}
+
+	public Query setFirstResult(int firstResult) {
+		this.firstResult = firstResult;
+		return this;
+	}
+
+	public Query setMaxResults(int maxResults) {
+		this.maxResults = maxResults;
+		return this;
+	}
+
+	public int executeUpdate() throws HibernateException {
+		throw new HibernateException( "Not supported operation" );
+	}
+
+	public Query setLockMode(String alias, LockMode lockMode) {
+		return null;
+	}
+
+	protected Map getLockModes() {
+		return null;
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/IteratorImpl.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/IteratorImpl.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/IteratorImpl.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,40 @@
+//$Id: $
+package org.hibernate.search.query;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.hibernate.Session;
+
+/**
+ * @author Emmanuel Bernard
+ */
+//TODO load the next batch-size elements to benefit from batch-size 
+public class IteratorImpl implements Iterator {
+
+	private final List<EntityInfo> entityInfos;
+	private final Session session;
+	private int index = 0;
+	private final int size;
+
+	public IteratorImpl(List<EntityInfo> entityInfos, Session session) {
+		this.entityInfos = entityInfos;
+		this.session = session;
+		this.size = entityInfos.size();
+	}
+
+	public boolean hasNext() {
+		return index < size;
+	}
+
+	public Object next() {
+		Object object = session.get( entityInfos.get( index ).clazz, entityInfos.get( index ).id );
+		index++;
+		return object;
+	}
+
+	public void remove() {
+		//TODO this is theorically doable
+		throw new UnsupportedOperationException( "Cannot remove from a lucene query iterator" );
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/ScrollableResultsImpl.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/ScrollableResultsImpl.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/query/ScrollableResultsImpl.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,218 @@
+//$Id: $
+package org.hibernate.search.query;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.search.Hits;
+import org.apache.lucene.search.Searcher;
+import org.hibernate.HibernateException;
+import org.hibernate.ScrollableResults;
+import org.hibernate.Session;
+import org.hibernate.search.engine.DocumentBuilder;
+import org.hibernate.search.event.FullTextIndexEventListener;
+import org.hibernate.type.Type;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class ScrollableResultsImpl implements ScrollableResults {
+	private final Searcher searcher;
+	private final Hits hits;
+	private final int first;
+	private final int max;
+	private int current;
+	private final Session session;
+	private final FullTextIndexEventListener listener;
+	private EntityInfo[] entityInfos;
+
+	public ScrollableResultsImpl(
+			Searcher searcher, Hits hits, int first, int max, Session session, FullTextIndexEventListener listener
+	) {
+		this.searcher = searcher;
+		this.hits = hits;
+		this.first = first;
+		this.max = max;
+		this.current = first;
+		this.session = session;
+		this.listener = listener;
+		entityInfos = new EntityInfo[max - first + 1];
+	}
+
+	public boolean next() throws HibernateException {
+		return ++current <= max;
+	}
+
+	public boolean previous() throws HibernateException {
+		return --current >= first;
+	}
+
+	public boolean scroll(int i) throws HibernateException {
+		current = current + i;
+		return current >= first && current <= max;
+	}
+
+	public boolean last() throws HibernateException {
+		current = max;
+		return max >= first;
+	}
+
+	public boolean first() throws HibernateException {
+		current = first;
+		return max >= first;
+	}
+
+	public void beforeFirst() throws HibernateException {
+		current = first - 1;
+	}
+
+	public void afterLast() throws HibernateException {
+		current = max + 1;
+	}
+
+	public boolean isFirst() throws HibernateException {
+		return current == first;
+	}
+
+	public boolean isLast() throws HibernateException {
+		return current == max;
+	}
+
+	public void close() throws HibernateException {
+		try {
+			searcher.close();
+		}
+		catch (IOException e) {
+			throw new HibernateException( "Unable to close Lucene searcher", e );
+		}
+	}
+
+	public Object[] get() throws HibernateException {
+		if ( current < first || current > max ) return null; //or exception?
+		EntityInfo info = entityInfos[current - first];
+		if ( info == null ) {
+			info = new EntityInfo();
+			Document document = null;
+			try {
+				document = hits.doc( current );
+			}
+			catch (IOException e) {
+				throw new HibernateException( "Unable to read Lucene hits[" + current + "]", e );
+			}
+			info.clazz = DocumentBuilder.getDocumentClass( document );
+			//FIXME should check that clazz match classes but this complexify a lot the firstResult/maxResult
+			info.id = DocumentBuilder.getDocumentId( listener, info.clazz, document );
+			entityInfos[current - first] = info;
+		}
+		return new Object[]{
+				session.get( info.clazz, info.id )
+		};
+	}
+
+	public Object get(int i) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public Type getType(int i) {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public Integer getInteger(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public Long getLong(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public Float getFloat(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public Boolean getBoolean(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public Double getDouble(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public Short getShort(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public Byte getByte(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public Character getCharacter(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public byte[] getBinary(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public String getText(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public Blob getBlob(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public Clob getClob(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public String getString(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public BigDecimal getBigDecimal(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public BigInteger getBigInteger(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public Date getDate(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public Locale getLocale(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public Calendar getCalendar(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public TimeZone getTimeZone(int col) throws HibernateException {
+		throw new UnsupportedOperationException( "Lucene does not work on columns" );
+	}
+
+	public int getRowNumber() throws HibernateException {
+		if ( max < first ) return -1;
+		return current - first;
+	}
+
+	public boolean setRowNumber(int rowNumber) throws HibernateException {
+		if ( rowNumber >= 0 ) {
+			current = first + rowNumber;
+		}
+		else {
+			current = max + rowNumber + 1; //max row start at -1
+		}
+		return current >= first && current <= max;
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/DirectoryProvider.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/DirectoryProvider.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/DirectoryProvider.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,30 @@
+//$Id: $
+package org.hibernate.search.store;
+
+import java.util.Properties;
+
+import org.apache.lucene.store.Directory;
+
+
+/**
+ * Set up and provide a Lucene <code>Directory</code>
+ * <code>equals()</code> and <code>hashCode()</code> must guaranty equality
+ * between two providers pointing to the same underlying Lucene Store
+ * This class must be thread safe regarding <code>getDirectory()</code>
+ * calls
+ *
+ * @author Emmanuel Bernard
+ * @author Sylvain Vieujot
+ */
+public interface DirectoryProvider<TDirectory extends Directory> {
+	/**
+	 * get the information to initialize the directory and build its hashCode
+	 */
+	void initialize(String directoryProviderName, Properties properties);
+
+	/**
+	 * Returns an initialized Lucene Directory. This method call <b>must</b> be threadsafe
+	 */
+	TDirectory getDirectory();
+}
+

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/DirectoryProviderFactory.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/DirectoryProviderFactory.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/DirectoryProviderFactory.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,128 @@
+//$Id: $
+package org.hibernate.search.store;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.hibernate.HibernateException;
+import org.hibernate.cfg.AnnotationConfiguration;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.mapping.PersistentClass;
+import org.hibernate.reflection.ReflectionManager;
+import org.hibernate.reflection.XClass;
+import org.hibernate.util.ReflectHelper;
+import org.hibernate.util.StringHelper;
+
+/**
+ * Create a Lucene directory provider
+ * <p/>
+ * Lucene directory providers are configured through properties
+ * - hibernate.search.default.* and
+ * - hibernate.search.<indexname>.*
+ * <p/>
+ * <indexname> properties have precedence over default
+ * <p/>
+ * The implementation is described by
+ * hibernate.search.[default|indexname].directory_provider
+ * <p/>
+ * If none is defined the default value is FSDirectory
+ *
+ * @author Emmanuel Bernard
+ * @author Sylvain Vieujot
+ */
+public class DirectoryProviderFactory {
+	public List<DirectoryProvider<?>> providers = new ArrayList<DirectoryProvider<?>>();
+	private static String LUCENE_PREFIX = "hibernate.search.";
+	private static String LUCENE_DEFAULT = LUCENE_PREFIX + "default.";
+	private static String DEFAULT_DIRECTORY_PROVIDER = FSDirectoryProvider.class.getName();
+
+	//TODO for the public?
+	//public DirectoryProvider<?> createDirectoryProvider(XClass entity, Configuration cfg) {
+
+	public DirectoryProvider<?> createDirectoryProvider(XClass entity, Configuration cfg) {
+		//get properties
+		String directoryProviderName = getDirectoryProviderName( entity, cfg );
+		Properties indexProps = getDirectoryProperties( cfg, directoryProviderName );
+
+		//set up the directory
+		String className = indexProps.getProperty( "directory_provider" );
+		if ( StringHelper.isEmpty( className ) ) {
+			className = DEFAULT_DIRECTORY_PROVIDER;
+		}
+		DirectoryProvider<?> provider = null;
+		try {
+			@SuppressWarnings( "unchecked" )
+			Class<DirectoryProvider> directoryClass = ReflectHelper.classForName(
+					className, DirectoryProviderFactory.class
+			);
+			provider = directoryClass.newInstance();
+		}
+		catch (Exception e) {
+			throw new HibernateException( "Unable to instanciate directory provider: " + className, e );
+		}
+		provider.initialize( directoryProviderName, indexProps );
+		int index = providers.indexOf( provider );
+		if ( index != -1 ) {
+			//share the same Directory provider for the same underlying store
+			return providers.get( index );
+		}
+		else {
+			providers.add( provider );
+			return provider;
+		}
+	}
+
+	private static Properties getDirectoryProperties(Configuration cfg, String directoryProviderName) {
+		Properties props = cfg.getProperties();
+		String indexName = LUCENE_PREFIX + directoryProviderName;
+		Properties indexProps = new Properties();
+		Properties indexSpecificProps = new Properties();
+		for ( Map.Entry entry : props.entrySet() ) {
+			String key = (String) entry.getKey();
+			if ( key.startsWith( LUCENE_DEFAULT ) ) {
+				indexProps.setProperty( key.substring( LUCENE_DEFAULT.length() ), (String) entry.getValue() );
+			}
+			else if ( key.startsWith( indexName ) ) {
+				indexSpecificProps.setProperty( key.substring( indexName.length() ), (String) entry.getValue() );
+			}
+		}
+		indexProps.putAll( indexSpecificProps );
+		return indexProps;
+	}
+
+	public static String getDirectoryProviderName(XClass clazz, Configuration cfg) {
+		//yuk
+		ReflectionManager reflectionManager =
+				( (AnnotationConfiguration) cfg ).createExtendedMappings().getReflectionManager();
+		//get the most specialized (ie subclass > superclass) non default index name
+		//if none extract the name from the most generic (superclass > subclass) @Indexed class in the hierarchy
+		//FIXME I'm inclined to get rid of the default value
+		PersistentClass pc = cfg.getClassMapping( clazz.getName() );
+		XClass rootIndex = null;
+		do {
+			XClass currentClazz = reflectionManager.toXClass( pc.getMappedClass() );
+			Indexed indexAnn = currentClazz.getAnnotation( Indexed.class );
+			if ( indexAnn != null ) {
+				if ( indexAnn.index().length() != 0 ) {
+					return indexAnn.index();
+				}
+				else {
+					rootIndex = currentClazz;
+				}
+			}
+			pc = pc.getSuperclass();
+		}
+		while ( pc != null );
+		//there is nobody outthere with a non default @Indexed.index
+		if ( rootIndex != null ) {
+			return rootIndex.getName();
+		}
+		else {
+			throw new HibernateException(
+					"Trying to extract the index name from a non @Indexed class: " + clazz.getName() );
+		}
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/FSDirectoryProvider.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/FSDirectoryProvider.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/FSDirectoryProvider.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,80 @@
+//$Id: $
+package org.hibernate.search.store;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.store.FSDirectory;
+import org.hibernate.HibernateException;
+
+/**
+ * Use a Lucene FSDirectory
+ * The base directory is represented by hibernate.search.<index>.indexBase
+ * The index is created in <base directory>/<index name>
+ *
+ * @author Emmanuel Bernard
+ * @author Sylvain Vieujot
+ */
+public class FSDirectoryProvider implements DirectoryProvider<FSDirectory> {
+	private FSDirectory directory;
+	private static Log log = LogFactory.getLog( FSDirectoryProvider.class );
+	private String indexName;
+
+	public void initialize(String directoryProviderName, Properties properties) {
+		String indexBase = properties.getProperty( "indexBase", "." );
+		File indexDir = new File( indexBase );
+
+		if ( !( indexDir.exists() && indexDir.isDirectory() ) ) {
+			//TODO create the directory?
+			throw new HibernateException( MessageFormat.format( "Index directory does not exists: {0}", indexBase ) );
+		}
+		if ( !indexDir.canWrite() ) {
+			throw new HibernateException( "Cannot write into index directory: " + indexBase );
+		}
+		log.info( "Setting index dir to " + indexDir );
+
+		File file = new File( indexDir, directoryProviderName );
+
+		try {
+			boolean create = !file.exists();
+			indexName = file.getCanonicalPath();
+			directory = FSDirectory.getDirectory( indexName, create );
+			if ( create ) {
+				IndexWriter iw = new IndexWriter( directory, new StandardAnalyzer(), create );
+				iw.close();
+			}
+		}
+		catch (IOException e) {
+			throw new HibernateException( "Unable to initialize index: " + directoryProviderName, e );
+		}
+
+	}
+
+	public FSDirectory getDirectory() {
+		return directory;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		// this code is actually broken since the value change after initialize call
+		// but from a practical POV this is fine since we only call this method
+		// after initialize call
+		if ( obj == this ) return true;
+		if ( obj == null || !( obj instanceof FSDirectoryProvider ) ) return false;
+		return indexName.equals( ( (FSDirectoryProvider) obj ).indexName );
+	}
+
+	@Override
+	public int hashCode() {
+		// this code is actually broken since the value change after initialize call
+		// but from a practical POV this is fine since we only call this method
+		// after initialize call
+		return indexName.hashCode();
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/RAMDirectoryProvider.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/RAMDirectoryProvider.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/RAMDirectoryProvider.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,57 @@
+//$Id: $
+package org.hibernate.search.store;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.store.RAMDirectory;
+import org.hibernate.HibernateException;
+
+/**
+ * Use a Lucene RAMDirectory
+ *
+ * @author Emmanuel Bernard
+ * @author Sylvain Vieujot
+ */
+public class RAMDirectoryProvider implements DirectoryProvider<RAMDirectory> {
+
+	private RAMDirectory directory;
+	private String indexName;
+
+	public void initialize(String directoryProviderName, Properties properties) {
+		indexName = directoryProviderName;
+		directory = new RAMDirectory();
+		try {
+			IndexWriter iw = new IndexWriter( directory, new StandardAnalyzer(), true );
+			iw.close();
+		}
+		catch (IOException e) {
+			throw new HibernateException( "Unable to initialize index: " + indexName, e );
+		}
+	}
+
+	public RAMDirectory getDirectory() {
+		return directory;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		// this code is actually broken since the value change after initialize call
+		// but from a practical POV this is fine since we only call this method
+		// after initialize call
+		if ( obj == this ) return true;
+		if ( obj == null || !( obj instanceof RAMDirectoryProvider ) ) return false;
+		return indexName.equals( ( (RAMDirectoryProvider) obj ).indexName );
+	}
+
+	@Override
+	public int hashCode() {
+		// this code is actually broken since the value change after initialize call
+		// but from a practical POV this is fine since we only call this method
+		// after initialize call
+		return indexName.hashCode();
+	}
+
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/util/BinderHelper.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/util/BinderHelper.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/util/BinderHelper.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,23 @@
+//$Id: $
+package org.hibernate.search.util;
+
+import org.hibernate.reflection.XMember;
+import org.hibernate.util.StringHelper;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public abstract class BinderHelper {
+
+	private BinderHelper() {
+	}
+
+	/**
+	 * Get attribute name out of member unless overriden by <code>name</code>
+	 */
+	public static String getAttributeName(XMember member, String name) {
+		return StringHelper.isNotEmpty( name ) ?
+				name :
+				member.getName(); //explicit field name
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/util/ContextHelper.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/util/ContextHelper.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/util/ContextHelper.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,27 @@
+//$Id: $
+package org.hibernate.search.util;
+
+import org.hibernate.HibernateException;
+import org.hibernate.engine.SessionImplementor;
+import org.hibernate.event.PostInsertEventListener;
+import org.hibernate.search.event.FullTextIndexEventListener;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public abstract class ContextHelper {
+
+	public static FullTextIndexEventListener getLuceneEventListener(SessionImplementor session) {
+		PostInsertEventListener[] listeners = session.getListeners().getPostInsertEventListeners();
+		FullTextIndexEventListener listener = null;
+		//FIXME this sucks since we mandante the event listener use
+		for ( PostInsertEventListener candidate : listeners ) {
+			if ( candidate instanceof FullTextIndexEventListener ) {
+				listener = (FullTextIndexEventListener) candidate;
+				break;
+			}
+		}
+		if ( listener == null ) throw new HibernateException( "Lucene event listener not initialized" );
+		return listener;
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/AlternateDocument.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/AlternateDocument.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/AlternateDocument.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,81 @@
+//$Id: $
+package org.hibernate.search.test;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Lob;
+
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.DocumentId;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Store;
+import org.hibernate.search.annotations.Index;
+import org.hibernate.search.annotations.Boost;
+
+/**
+ * Example of 2 entities mapped in the same index
+ * @author Emmanuel Bernard
+ */
+ at Entity
+ at Indexed(index = "Documents")
+public class AlternateDocument {
+	private Long id;
+	private String title;
+	private String summary;
+	private String text;
+
+	AlternateDocument() {
+	}
+
+	public AlternateDocument(Long id, String title, String summary, String text) {
+		super();
+		this.id = id;
+		this.summary = summary;
+		this.text = text;
+		this.title = title;
+	}
+
+	@Id
+	//@Keyword(id = true)
+	@DocumentId()
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	//@Text
+	@Field( name = "alt_title", store = Store.YES, index = Index.TOKENIZED )
+	@Boost(2)
+	public String getTitle() {
+		return title;
+	}
+
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	//@Unstored(name = "Abstract")
+	@Field( name="Abstract", store = Store.NO, index = Index.TOKENIZED )
+	public String getSummary() {
+		return summary;
+	}
+
+	public void setSummary(String summary) {
+		this.summary = summary;
+	}
+
+	@Lob
+	//@Unstored
+	@Field( store = Store.NO, index = Index.TOKENIZED )
+	public String getText() {
+		return text;
+	}
+
+	public void setText(String text) {
+		this.text = text;
+	}
+}
+

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/Clock.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/Clock.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/Clock.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,41 @@
+//$Id: $
+package org.hibernate.search.test;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.Text;
+import org.hibernate.search.annotations.Keyword;
+
+/**
+ * @author Emmanuel Bernard
+ */
+ at Entity
+ at Indexed
+public class Clock {
+	private Integer id;
+	private String brand;
+
+	public Clock(Integer id, String brand) {
+		this.id = id;
+		this.brand = brand;
+	}
+
+	@Text public String getBrand() {
+		return brand;
+	}
+
+	public void setBrand(String brand) {
+		this.brand = brand;
+	}
+
+	@Id @Keyword
+	public Integer getId() {
+		return id;
+	}
+
+	public void setId(Integer id) {
+		this.id = id;
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/Document.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/Document.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/Document.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,77 @@
+//$Id: Document.java 10742 2006-11-07 01:03:16Z epbernard $
+package org.hibernate.search.test;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Lob;
+
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.Boost;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Index;
+import org.hibernate.search.annotations.DocumentId;
+import org.hibernate.search.annotations.Store;
+
+ at Entity
+ at Indexed(index = "Documents")
+public class Document {
+	private Long id;
+	private String title;
+	private String summary;
+	private String text;
+
+	Document() {
+	}
+
+	public Document(String title, String summary, String text) {
+		super();
+		this.summary = summary;
+		this.text = text;
+		this.title = title;
+	}
+
+	@Id
+	@GeneratedValue
+	//@Keyword(id = true)
+    @DocumentId
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	//@Text
+    @Field( store = Store.YES, index = Index.TOKENIZED )
+    @Boost(2)
+	public String getTitle() {
+		return title;
+	}
+
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	//@Unstored(name = "Abstract")
+    @Field( name="Abstract", store = Store.NO, index = Index.TOKENIZED )
+    public String getSummary() {
+		return summary;
+	}
+
+	public void setSummary(String summary) {
+		this.summary = summary;
+	}
+
+	@Lob
+	//@Unstored
+    @Field( store = Store.NO, index = Index.TOKENIZED )
+    public String getText() {
+		return text;
+	}
+
+	public void setText(String text) {
+		this.text = text;
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/FSDirectoryTest.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/FSDirectoryTest.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/FSDirectoryTest.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,193 @@
+//$Id: LuceneTest.java 10014 2006-06-12 09:56:27 -0700 (lun., 12 juin 2006) epbernard $
+package org.hibernate.search.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.search.Environment;
+import org.hibernate.search.store.FSDirectoryProvider;
+import org.hibernate.search.event.FullTextIndexEventListener;
+
+/**
+ * @author Gavin King
+ */
+public class FSDirectoryTest 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 Document( "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();
+		Document entity = (Document) s.get( Document.class, new Long( 1 ) );
+		entity.setSummary( "Object/relational mapping with EJB3" );
+		s.persist( new Document( "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( Document.class ).uniqueResult() );
+		s.getTransaction().commit();
+		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() );
+			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 Document" ).list();
+		for ( Document document : (List<Document>) list ) {
+			s.delete( document );
+		}
+		s.getTransaction().commit();
+		s.close();
+	}
+
+	protected Class[] getMappings() {
+		return new Class[]{
+				Document.class
+		};
+	}
+
+	protected void configure(org.hibernate.cfg.Configuration cfg) {
+		File sub = getBaseIndexDir();
+		cfg.setProperty( "hibernate.search.default.indexBase", sub.getAbsolutePath() );
+		cfg.setProperty( "hibernate.search.Clock.directory_provider", FSDirectoryProvider.class.getName() );
+		cfg.setProperty( Environment.ANALYZER_CLASS, StopAnalyzer.class.getName() );
+		FullTextIndexEventListener del = new FullTextIndexEventListener();
+		cfg.getEventListeners().setPostDeleteEventListeners( new PostDeleteEventListener[]{del} );
+		cfg.getEventListeners().setPostUpdateEventListeners( new PostUpdateEventListener[]{del} );
+		cfg.getEventListeners().setPostInsertEventListeners( new PostInsertEventListener[]{del} );
+	}
+
+}
+

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/RamDirectoryTest.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/RamDirectoryTest.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/RamDirectoryTest.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,71 @@
+//$Id: $
+package org.hibernate.search.test;
+
+import java.io.File;
+import java.util.List;
+
+import org.hibernate.Session;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Hits;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class RamDirectoryTest extends TestCase {
+
+	public void testMultipleEntitiesPerIndex() throws Exception {
+
+
+		Session s = getSessions().openSession();
+		s.getTransaction().begin();
+		Document document =
+				new Document( "Hibernate in Action", "Object/relational mapping with Hibernate", "blah blah blah" );
+		s.persist(document);
+		s.flush();
+		s.persist(
+				new AlternateDocument( document.getId(), "Hibernate in Action", "Object/relational mapping with Hibernate", "blah blah blah" )
+		);
+		s.getTransaction().commit();
+		s.close();
+
+		assertEquals( 2, getDocumentNbr() );
+
+		s = getSessions().openSession();
+		s.getTransaction().begin();
+		s.delete( s.get( AlternateDocument.class, document.getId() ) );
+		s.getTransaction().commit();
+		s.close();
+
+		assertEquals( 1, getDocumentNbr() );
+
+		s = getSessions().openSession();
+		s.getTransaction().begin();
+		s.delete( s.createCriteria( Document.class ).uniqueResult() );
+		s.getTransaction().commit();
+		s.close();
+	}
+
+	private int getDocumentNbr() throws Exception {
+		IndexReader reader = IndexReader.open( getDirectory( Document.class ) );
+		try {
+			return reader.numDocs();
+		}
+		finally {
+			reader.close();
+		}
+	}
+
+	protected Class[] getMappings() {
+		return new Class[]{
+				Document.class,
+				AlternateDocument.class
+		};
+	}
+
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/TestCase.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/TestCase.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/TestCase.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,50 @@
+//$Id: $
+package org.hibernate.search.test;
+
+import org.apache.lucene.analysis.StopAnalyzer;
+import org.apache.lucene.store.Directory;
+import org.hibernate.event.PostDeleteEventListener;
+import org.hibernate.event.PostInsertEventListener;
+import org.hibernate.event.PostUpdateEventListener;
+import org.hibernate.search.Environment;
+import org.hibernate.search.store.RAMDirectoryProvider;
+import org.hibernate.search.event.FullTextIndexEventListener;
+import org.hibernate.HibernateException;
+import org.hibernate.impl.SessionFactoryImpl;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public abstract class TestCase extends org.hibernate.test.annotations.TestCase {
+	protected void setUp() throws Exception {
+		//super.setUp(); //we need a fresh session factory each time for index set up
+		buildSessionFactory( getMappings(), getAnnotatedPackages(), getXmlFiles() );
+	}
+
+	protected Directory getDirectory(Class clazz) {
+		return getLuceneEventListener().getDocumentBuilders().get( clazz ).getDirectoryProvider().getDirectory();
+	}
+
+	private FullTextIndexEventListener getLuceneEventListener() {
+        PostInsertEventListener[] listeners = ( (SessionFactoryImpl) getSessions() ).getEventListeners().getPostInsertEventListeners();
+        FullTextIndexEventListener listener = null;
+        //FIXME this sucks since we mandante the event listener use
+        for (PostInsertEventListener candidate : listeners) {
+            if (candidate instanceof FullTextIndexEventListener ) {
+                listener = (FullTextIndexEventListener) candidate;
+                break;
+            }
+        }
+        if (listener == null) throw new HibernateException("Lucene event listener not initialized");
+        return listener;
+    }
+
+	protected void configure(org.hibernate.cfg.Configuration cfg) {
+		cfg.setProperty( "hibernate.search.default.directory_provider", RAMDirectoryProvider.class.getName() );
+		cfg.setProperty( Environment.ANALYZER_CLASS, StopAnalyzer.class.getName() );
+		FullTextIndexEventListener del = new FullTextIndexEventListener();
+		cfg.getEventListeners().setPostDeleteEventListeners( new PostDeleteEventListener[]{del} );
+		cfg.getEventListeners().setPostUpdateEventListeners( new PostUpdateEventListener[]{del} );
+		cfg.getEventListeners().setPostInsertEventListeners( new PostInsertEventListener[]{del} );
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/TransactionTest.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/TransactionTest.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/TransactionTest.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,61 @@
+//$Id: $
+package org.hibernate.search.test;
+
+import java.io.IOException;
+
+import org.hibernate.Session;
+import org.apache.lucene.index.IndexReader;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class TransactionTest extends TestCase {
+
+	public void testTransactionCommit() throws Exception {
+		Session s = getSessions().openSession();
+		s.getTransaction().begin();
+		s.persist(
+				new Document( "Hibernate in Action", "Object/relational mapping with Hibernate", "blah blah blah" )
+		);
+		s.getTransaction().commit();
+		s.close();
+
+		assertEquals( "transaction.commit() should no index", 1, getDocumentNumber() );
+
+		s = getSessions().openSession();
+		s.getTransaction().begin();
+		s.persist(
+				new Document( "Java Persistence with Hibernate", "Object/relational mapping with Hibernate", "blah blah blah" )
+		);
+		s.flush();
+		s.getTransaction().rollback();
+		s.close();
+
+		assertEquals( "rollback() should not index", 1, getDocumentNumber() );
+
+		s = getSessions().openSession();
+		s.persist(
+				new Document( "Java Persistence with Hibernate", "Object/relational mapping with Hibernate", "blah blah blah" )
+		);
+		s.flush();
+		s.close();
+
+		assertEquals( "no transaction should index", 2, getDocumentNumber() );
+
+	}
+
+	private int getDocumentNumber() throws IOException {
+		IndexReader reader = IndexReader.open( getDirectory( Document.class ) );
+		try {
+			return reader.numDocs();
+		}
+		finally {
+			reader.close();
+		}
+	}
+
+
+	protected Class[] getMappings() {
+		return new Class[]{Document.class};
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/BridgeTest.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/BridgeTest.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/BridgeTest.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,150 @@
+//$Id: $
+package org.hibernate.search.test.bridge;
+
+import java.util.Date;
+import java.util.List;
+import java.util.GregorianCalendar;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+import org.hibernate.Transaction;
+import org.hibernate.cfg.Configuration;
+
+import org.hibernate.search.test.TestCase;
+import org.hibernate.search.Environment;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.search.Search;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.analysis.SimpleAnalyzer;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class BridgeTest extends TestCase {
+    public void testDefaultAndNullBridges() throws Exception {
+        Cloud cloud = new Cloud();
+        cloud.setDate( null );
+        cloud.setDouble1( null );
+        cloud.setDouble2( 2.1d );
+        cloud.setInt1( null );
+        cloud.setInt2( 2 );
+        cloud.setFloat1( null );
+        cloud.setFloat2( 2.1f );
+        cloud.setLong1( null );
+        cloud.setLong2( 2l );
+        cloud.setString(null);
+        org.hibernate.Session s = openSession();
+        Transaction tx = s.beginTransaction();
+        s.persist(cloud);
+        s.flush();
+        tx.commit();
+
+        tx = s.beginTransaction();
+        FullTextSession session = Search.createFullTextSession(s);
+        QueryParser parser = new QueryParser("id", new StandardAnalyzer() );
+        Query query;
+        List result;
+
+        query = parser.parse("double2:[2.1 TO 2.1] AND float2:[2.1 TO 2.1] AND int2:[2 TO 2.1] AND long2:[2 TO 2.1]");
+        result = session.createFullTextQuery(query).list();
+        assertEquals( "find primitives and do not fail on null", 1, result.size() );
+
+        query = parser.parse("double1:[2.1 TO 2.1] OR float1:[2.1 TO 2.1] OR int1:[2 TO 2.1] OR long1:[2 TO 2.1]");
+        result = session.createFullTextQuery(query).list();
+        assertEquals( "null elements should not be stored", 0, result.size() ); //the query is dumb because restrictive
+
+        s.delete( s.get( Cloud.class, cloud.getId() ) );
+        tx.commit();
+        s.close();
+
+    }
+
+    public void testCustomBridges() throws Exception {
+        Cloud cloud = new Cloud();
+        cloud.setCustomFieldBridge( "This is divided by 2");
+        cloud.setCustomStringBridge( "This is div by 4");
+        org.hibernate.Session s = openSession();
+        Transaction tx = s.beginTransaction();
+        s.persist(cloud);
+        s.flush();
+        tx.commit();
+
+        tx = s.beginTransaction();
+        FullTextSession session = Search.createFullTextSession(s);
+        QueryParser parser = new QueryParser("id", new SimpleAnalyzer() );
+        Query query;
+        List result;
+
+        query = parser.parse("customFieldBridge:This AND customStringBridge:This");
+        result = session.createFullTextQuery(query).list();
+        assertEquals( "Properties not mapped", 1, result.size() );
+
+        query = parser.parse("customFieldBridge:by AND customStringBridge:is");
+        result = session.createFullTextQuery(query).list();
+        assertEquals( "Custom types not taken into account", 0, result.size() );
+
+        s.delete( s.get( Cloud.class, cloud.getId() ) );
+        tx.commit();
+        s.close();
+
+    }
+
+    public void testDateBridge() throws Exception {
+        Cloud cloud = new Cloud();
+        Calendar c = GregorianCalendar.getInstance();
+        c.setTimeZone( TimeZone.getTimeZone( "GMT" ) ); //for the sake of tests
+        c.set(2000, 11, 15, 3, 43, 2);
+        c.set( Calendar.MILLISECOND, 5 );
+
+        Date date = new Date( c.getTimeInMillis() );
+        cloud.setDate( date ); //5 millisecond
+        cloud.setDateDay( date );
+        cloud.setDateHour( date );
+        cloud.setDateMillisecond( date );
+        cloud.setDateMinute( date );
+        cloud.setDateMonth( date );
+        cloud.setDateSecond( date );
+        cloud.setDateYear( date );
+        org.hibernate.Session s = openSession();
+        Transaction tx = s.beginTransaction();
+        s.persist(cloud);
+        s.flush();
+        tx.commit();
+
+        tx = s.beginTransaction();
+        FullTextSession session = Search.createFullTextSession(s);
+        QueryParser parser = new QueryParser("id", new StandardAnalyzer() );
+        Query query;
+        List result;
+
+        query = parser.parse("date:[19900101 TO 20060101]"
+                + " AND dateDay:[20001214 TO 2000121501]"
+                + " AND dateMonth:[200012 TO 20001201]"
+                + " AND dateYear:[2000 TO 200001]"
+                + " AND dateHour:[20001214 TO 2000121503]"
+                + " AND dateMinute:[20001214 TO 200012150343]"
+                + " AND dateSecond:[20001214 TO 20001215034302]"
+                + " AND dateMillisecond:[20001214 TO 20001215034302005]"
+        );
+        result = session.createFullTextQuery(query).list();
+        assertEquals( "Date not found or not property truncated", 1, result.size() );
+
+        s.delete( s.get( Cloud.class, cloud.getId() ) );
+        tx.commit();
+        s.close();
+
+    }
+    protected Class[] getMappings() {
+        return new Class[] {
+                Cloud.class
+        };
+    }
+
+
+    protected void configure(Configuration cfg) {
+        super.configure( cfg );
+        cfg.setProperty( Environment.ANALYZER_CLASS, SimpleAnalyzer.class.getName() );
+    }
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/Cloud.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/Cloud.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/Cloud.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,233 @@
+//$Id: $
+package org.hibernate.search.test.bridge;
+
+import java.util.Date;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.GeneratedValue;
+
+import org.hibernate.search.annotations.Keyword;
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.Text;
+import org.hibernate.search.annotations.DateBridge;
+import org.hibernate.search.annotations.FieldBridge;
+import org.hibernate.search.annotations.Resolution;
+import org.hibernate.annotations.Parameter;
+
+/**
+ * @author Emmanuel Bernard
+ */
+ at Entity
+ at Indexed
+public class Cloud {
+    private int id;
+    private Long long1;
+    private long long2;
+    private Integer int1;
+    private int int2;
+    private Double double1;
+    private double double2;
+    private Float float1;
+    private float float2;
+    private String string;
+    private Date date;
+    private Date dateYear;
+    private Date dateMonth;
+    private Date dateDay;
+    private Date dateHour;
+    private Date dateMinute;
+    private Date dateSecond;
+    private Date dateMillisecond;
+    private String customFieldBridge;
+    private String customStringBridge;
+
+    @Text
+    @FieldBridge(impl = TruncateFieldBridge.class)
+    public String getCustomFieldBridge() {
+        return customFieldBridge;
+    }
+
+    public void setCustomFieldBridge(String customFieldBridge) {
+        this.customFieldBridge = customFieldBridge;
+    }
+
+    @Text
+    @FieldBridge(impl = TruncateStringBridge.class, params = @Parameter( name="dividedBy", value="4" ) )
+    public String getCustomStringBridge() {
+        return customStringBridge;
+    }
+
+    public void setCustomStringBridge(String customStringBridge) {
+        this.customStringBridge = customStringBridge;
+    }
+
+    @Id @GeneratedValue @Keyword(id=true)
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    @Keyword
+    public Long getLong1() {
+        return long1;
+    }
+
+    public void setLong1(Long long1) {
+        this.long1 = long1;
+    }
+
+    @Keyword
+    public long getLong2() {
+        return long2;
+    }
+
+    public void setLong2(long long2) {
+        this.long2 = long2;
+    }
+
+    @Keyword
+    public Integer getInt1() {
+        return int1;
+    }
+
+    public void setInt1(Integer int1) {
+        this.int1 = int1;
+    }
+
+    @Keyword
+    public int getInt2() {
+        return int2;
+    }
+
+    public void setInt2(int int2) {
+        this.int2 = int2;
+    }
+
+    @Keyword
+    public Double getDouble1() {
+        return double1;
+    }
+
+    public void setDouble1(Double double1) {
+        this.double1 = double1;
+    }
+
+    @Keyword
+    public double getDouble2() {
+        return double2;
+    }
+
+    public void setDouble2(double double2) {
+        this.double2 = double2;
+    }
+
+    @Keyword
+    public Float getFloat1() {
+        return float1;
+    }
+
+    public void setFloat1(Float float1) {
+        this.float1 = float1;
+    }
+
+    @Keyword
+    public float getFloat2() {
+        return float2;
+    }
+
+    public void setFloat2(float float2) {
+        this.float2 = float2;
+    }
+
+    @Text
+	public String getString() {
+        return string;
+    }
+
+    public void setString(String string) {
+        this.string = string;
+    }
+
+    @Keyword
+    public Date getDate() {
+        return date;
+    }
+
+    public void setDate(Date date) {
+        this.date = date;
+    }
+
+    @Keyword
+    @DateBridge( resolution = Resolution.YEAR )
+    public Date getDateYear() {
+        return dateYear;
+    }
+
+    public void setDateYear(Date dateYear) {
+        this.dateYear = dateYear;
+    }
+
+    @Keyword
+    @DateBridge( resolution = Resolution.MONTH )
+    public Date getDateMonth() {
+        return dateMonth;
+    }
+
+    public void setDateMonth(Date dateMonth) {
+        this.dateMonth = dateMonth;
+    }
+
+    @Keyword
+    @DateBridge( resolution = Resolution.DAY )
+    public Date getDateDay() {
+        return dateDay;
+    }
+
+    public void setDateDay(Date dateDay) {
+        this.dateDay = dateDay;
+    }
+
+    @Keyword
+    @DateBridge( resolution = Resolution.HOUR )
+    public Date getDateHour() {
+        return dateHour;
+    }
+
+    public void setDateHour(Date dateHour) {
+        this.dateHour = dateHour;
+    }
+
+
+    @Keyword
+    @DateBridge( resolution = Resolution.MINUTE )
+    public Date getDateMinute() {
+        return dateMinute;
+    }
+
+    public void setDateMinute(Date dateMinute) {
+        this.dateMinute = dateMinute;
+    }
+
+    @Keyword
+    @DateBridge( resolution = Resolution.SECOND )
+    public Date getDateSecond() {
+        return dateSecond;
+    }
+
+    public void setDateSecond(Date dateSecond) {
+        this.dateSecond = dateSecond;
+    }
+
+    @Keyword
+	@DateBridge( resolution = Resolution.MILLISECOND )
+    public Date getDateMillisecond() {
+        return dateMillisecond;
+    }
+
+    public void setDateMillisecond(Date dateMillisecond) {
+        this.dateMillisecond = dateMillisecond;
+    }
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/DateSplitBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/DateSplitBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/DateSplitBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,43 @@
+//$Id: $
+package org.hibernate.search.test.bridge;
+
+import java.util.Date;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.hibernate.search.bridge.FieldBridge;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+
+/**
+ * Store the date in 3 different field year, month, day
+ * to ease Range Query per year, month or day
+ * (eg get all the elements of december for the last 5 years)
+ *
+ * @author Emmanuel Bernard
+ */
+public class DateSplitBridge implements FieldBridge {
+	private final static TimeZone GMT = TimeZone.getTimeZone("GMT");
+
+	public void set(String name, Object value, Document document, Field.Store store, Field.Index index, Float boost) {
+		Date date = (Date) value;
+		Calendar cal = GregorianCalendar.getInstance( GMT );
+		cal.setTime( date );
+		int year = cal.get( Calendar.YEAR );
+		int month = cal.get( Calendar.MONTH ) + 1;
+		int day = cal.get( Calendar.DAY_OF_MONTH );
+		//set year
+		Field field = new Field( name + ".year", String.valueOf(year), store, index );
+		if ( boost != null ) field.setBoost( boost );
+		document.add( field );
+		//set month and pad it if needed
+		field = new Field( name + ".month", month < 10 ? "0" : "" + String.valueOf(month), store, index );
+		if ( boost != null ) field.setBoost( boost );
+		document.add( field );
+		//set day and pad it if needed
+		field = new Field( name + ".day", day < 10 ? "0" : "" + String.valueOf(day), store, index );
+		if ( boost != null ) field.setBoost( boost );
+		document.add( field );
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/PaddedIntegerBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/PaddedIntegerBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/PaddedIntegerBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,44 @@
+//$Id: $
+package org.hibernate.search.test.bridge;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.HashMap;
+
+import org.hibernate.search.bridge.StringBridge;
+import org.hibernate.search.bridge.ParameterizedBridge;
+import org.hibernate.search.bridge.TwoWayStringBridge;
+import org.hibernate.search.annotations.FieldBridge;
+import org.hibernate.annotations.Parameter;
+
+/**
+ * Padding Integer bridge.
+ * All numbers will be padded with 0 to match 5 digits
+ *
+ * @author Emmanuel Bernard
+ */
+public class PaddedIntegerBridge implements TwoWayStringBridge, ParameterizedBridge {
+
+	public static String PADDING_PROPERTY = "padding";
+
+	private int padding = 5; //default
+
+	public void setParameterValues(Map parameters) {
+		Object padding = parameters.get( PADDING_PROPERTY );
+		if (padding != null) this.padding = (Integer) padding;
+	}
+
+	public String objectToString(Object object) {
+		String rawInteger = ( (Integer) object ).toString();
+		if (rawInteger.length() > padding) throw new IllegalArgumentException( "Try to pad on a number too big" );
+		StringBuilder paddedInteger = new StringBuilder( );
+		for ( int padIndex = rawInteger.length() ; padIndex < padding ; padIndex++ ) {
+			paddedInteger.append('0');
+		}
+		return paddedInteger.append( rawInteger ).toString();
+	}
+
+	public Object stringToObject(String stringValue) {
+		return new Integer(stringValue);
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/TruncateFieldBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/TruncateFieldBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/TruncateFieldBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,27 @@
+//$Id: $
+package org.hibernate.search.test.bridge;
+
+import org.hibernate.search.bridge.FieldBridge;
+import org.hibernate.util.StringHelper;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class TruncateFieldBridge implements FieldBridge {
+    public Object get(String name, Document document) {
+		Field field = document.getField( name );
+		return field.stringValue();
+	}
+
+	public void set(String name, Object value, Document document, Field.Store store, Field.Index index, Float boost) {
+        String indexedString = (String) value;
+        //Do not add fields on empty strings, seems a sensible default in most situations
+        if ( StringHelper.isNotEmpty( indexedString ) ) {
+            Field field = new Field(name, indexedString.substring(0, indexedString.length() / 2), store, index);
+            if (boost != null) field.setBoost( boost );
+            document.add( field );
+        }
+    }
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/TruncateStringBridge.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/TruncateStringBridge.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/bridge/TruncateStringBridge.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,26 @@
+//$Id: $
+package org.hibernate.search.test.bridge;
+
+import java.util.Map;
+
+import org.hibernate.search.bridge.StringBridge;
+import org.hibernate.search.bridge.ParameterizedBridge;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class TruncateStringBridge implements StringBridge, ParameterizedBridge {
+    private int div;
+    public Object stringToObject(String stringValue) {
+        return stringValue;
+    }
+
+    public String objectToString(Object object) {
+        String string = (String) object;
+        return object != null ? string.substring( 0, string.length() / div ) : null;
+    }
+
+    public void setParameterValues(Map parameters) {
+        div = Integer.valueOf( (String) parameters.get( "dividedBy" ) );
+    }
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/fieldAccess/Document.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/fieldAccess/Document.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/fieldAccess/Document.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,79 @@
+//$Id: $
+package org.hibernate.search.test.fieldAccess;
+
+import javax.persistence.Id;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Entity;
+import javax.persistence.Lob;
+
+import org.hibernate.search.annotations.Unstored;
+import org.hibernate.search.annotations.Text;
+import org.hibernate.search.annotations.Keyword;
+import org.hibernate.search.annotations.Boost;
+import org.hibernate.search.annotations.Indexed;
+
+/**
+ * @author Richard Hallier
+ */
+ at Entity
+ at Indexed(index = "DocumentField")
+public class Document {
+	@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;
+
+	Document() {
+	}
+
+	public Document(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;
+	}
+}
+

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/fieldAccess/FieldAccessTest.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/fieldAccess/FieldAccessTest.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/fieldAccess/FieldAccessTest.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,69 @@
+//$Id: $
+package org.hibernate.search.test.fieldAccess;
+
+import java.util.List;
+
+import org.hibernate.search.test.TestCase;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.search.Search;
+import org.hibernate.Session;
+import org.hibernate.Transaction;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class FieldAccessTest extends TestCase {
+
+    public void testFields() throws Exception {
+        Document doc = new Document( "Hibernate in Action", "Object/relational mapping with Hibernate", "blah blah blah" );
+        Session s = openSession();
+        Transaction tx = s.beginTransaction();
+        s.persist( doc );
+        tx.commit();
+
+        s.clear();
+
+        FullTextSession session = Search.createFullTextSession(s);
+        tx = session.beginTransaction();
+        QueryParser p = new QueryParser("id", new StandardAnalyzer( ) );
+        List result = session.createFullTextQuery( p.parse( "Abstract:Hibernate" ) ).list();
+        assertEquals( "Query by field", 1, result.size() );
+        s.delete( result.get( 0 ) );
+        tx.commit();
+        s.close();
+
+    }
+
+    public void testFieldBoost() throws Exception {
+        Session s = openSession();
+        Transaction tx = s.beginTransaction();
+        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" )
+		);
+        tx.commit();
+
+        s.clear();
+
+        FullTextSession session = Search.createFullTextSession(s);
+        tx = session.beginTransaction();
+        QueryParser p = new QueryParser("id", new StandardAnalyzer( ) );
+        List result = session.createFullTextQuery( p.parse( "title:Action OR Abstract:Action" ) ).list();
+        assertEquals( "Query by field", 2, result.size() );
+        assertEquals( "@Boost fails", "Hibernate in Action", ( (Document) result.get( 0 ) ).getTitle() );
+        s.delete( result.get( 0 ) );
+        tx.commit();
+        s.close();
+
+    }
+
+    protected Class[] getMappings() {
+        return new Class[] {
+                Document.class
+        };
+    }
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/inheritance/Animal.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/inheritance/Animal.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/inheritance/Animal.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,38 @@
+//$Id: $
+package org.hibernate.search.test.inheritance;
+
+import org.hibernate.search.annotations.Keyword;
+import org.hibernate.search.annotations.Text;
+import org.hibernate.search.annotations.Indexed;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.GeneratedValue;
+
+/**
+ * @author Emmanuel Bernard
+ */
+ at Entity
+ at Indexed
+public class Animal {
+    private Long id;
+    private String name;
+
+    @Id @GeneratedValue @Keyword(id=true) 
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    @Text
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/inheritance/InheritanceTest.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/inheritance/InheritanceTest.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/inheritance/InheritanceTest.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,67 @@
+//$Id: $
+package org.hibernate.search.test.inheritance;
+
+import org.hibernate.search.test.TestCase;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.search.Search;
+import org.hibernate.Transaction;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.analysis.StopAnalyzer;
+import org.apache.lucene.search.Query;
+
+import java.util.List;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class InheritanceTest extends TestCase {
+
+	public void testInheritance() throws Exception {
+		FullTextSession s = Search.createFullTextSession( openSession() );
+		Transaction tx = s.beginTransaction();
+		Animal a = new Animal();
+        a.setName("Shark Jr");
+        s.save( a );
+        Mammal m = new Mammal();
+        m.setMammalNbr(2);
+        m.setName("Elephant Jr");
+        s.save(m);
+		tx.commit();//post commit events for lucene
+		s.clear();
+		tx = s.beginTransaction();
+		QueryParser parser = new QueryParser("name", new StopAnalyzer() );
+
+		Query query;
+		org.hibernate.Query hibQuery;
+
+        query = parser.parse( "Elephant" );
+		hibQuery = s.createFullTextQuery( query, Mammal.class );
+		List result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( "Query subclass by superclass attribute", 1, result.size() );
+
+        query = parser.parse( "mammalNbr:[2 TO 2]" );
+		hibQuery = s.createFullTextQuery( query, Animal.class, Mammal.class );
+		result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( "Query subclass by subclass attribute", 1, result.size() );
+
+        query = parser.parse( "Jr" );
+		hibQuery = s.createFullTextQuery( query, Animal.class );
+		result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( "Query filtering on superclass return mapped subclasses", 2, result.size() );
+        for (Object managedEntity : result) {
+            s.delete(managedEntity);
+        }
+        tx.commit();
+		s.close();
+	}
+
+	protected Class[] getMappings() {
+		return new Class[] {
+                Animal.class,
+                Mammal.class
+        };
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/inheritance/Mammal.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/inheritance/Mammal.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/inheritance/Mammal.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,25 @@
+//$Id: $
+package org.hibernate.search.test.inheritance;
+
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.Keyword;
+
+import javax.persistence.Entity;
+
+/**
+ * @author Emmanuel Bernard
+ */
+ at Entity
+ at Indexed
+public class Mammal extends Animal {
+    private int mammalNbr;
+
+    @Keyword
+	public int getMammalNbr() {
+        return mammalNbr;
+    }
+
+    public void setMammalNbr(int mammalNbr) {
+        this.mammalNbr = mammalNbr;
+    }
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/AlternateBook.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/AlternateBook.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/AlternateBook.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,47 @@
+//$Id: $
+package org.hibernate.search.test.query;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.DocumentId;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Index;
+
+/**
+ * @author Emmanuel Bernard
+ */
+ at Entity
+ at Indexed(index="Book")
+public class AlternateBook {
+	@Id @DocumentId
+	private Integer id;
+	@Field(index = Index.TOKENIZED)
+	private String summary;
+
+
+	public AlternateBook() {
+	}
+
+	public AlternateBook(Integer id, String summary) {
+		this.id = id;
+		this.summary = summary;
+	}
+
+	public Integer getId() {
+		return id;
+	}
+
+	public void setId(Integer id) {
+		this.id = id;
+	}
+
+	public String getSummary() {
+		return summary;
+	}
+
+	public void setSummary(String summary) {
+		this.summary = summary;
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/Book.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/Book.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/Book.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,58 @@
+//$Id: $
+package org.hibernate.search.test.query;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.Text;
+import org.hibernate.search.annotations.Keyword;
+import org.hibernate.search.annotations.Unstored;
+
+/**
+ * @author Emmanuel Bernard
+ */
+ at Entity
+ at Indexed(index = "Book" )
+public class Book {
+
+	private Integer id;
+	private String body;
+	private String summary;
+
+	public Book() {
+	}
+
+	public Book(Integer id, String summary, String body) {
+		this.id = id;
+		this.summary = summary;
+		this.body = body;
+	}
+
+	@Unstored
+	public String getBody() {
+		return body;
+	}
+
+	public void setBody(String body) {
+		this.body = body;
+	}
+
+	@Id @Keyword(id=true)
+	public Integer getId() {
+		return id;
+	}
+
+	public void setId(Integer id) {
+		this.id = id;
+	}
+
+	@Text
+	public String getSummary() {
+		return summary;
+	}
+
+	public void setSummary(String summary) {
+		this.summary = summary;
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/Clock.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/Clock.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/Clock.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,44 @@
+//$Id: $
+package org.hibernate.search.test.query;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.Text;
+import org.hibernate.search.annotations.Keyword;
+
+/**
+ * @author Emmanuel Bernard
+ */
+ at Entity
+ at Indexed
+public class Clock {
+	private Integer id;
+	private String brand;
+
+	public Clock() {
+	}
+
+	public Clock(Integer id, String brand) {
+		this.id = id;
+		this.brand = brand;
+	}
+
+	@Text public String getBrand() {
+		return brand;
+	}
+
+	public void setBrand(String brand) {
+		this.brand = brand;
+	}
+
+	@Id @Keyword(id=true)
+	public Integer getId() {
+		return id;
+	}
+
+	public void setId(Integer id) {
+		this.id = id;
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/LuceneQueryTest.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/LuceneQueryTest.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/query/LuceneQueryTest.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,255 @@
+//$Id: $
+package org.hibernate.search.test.query;
+
+import java.util.List;
+import java.util.Iterator;
+
+import org.hibernate.search.test.TestCase;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.search.Search;
+import org.hibernate.Transaction;
+import org.hibernate.Hibernate;
+import org.hibernate.ScrollableResults;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.analysis.StopAnalyzer;
+
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class LuceneQueryTest extends TestCase {
+
+	public void testList() throws Exception {
+		FullTextSession s = Search.createFullTextSession( openSession() );
+		Transaction tx = s.beginTransaction();
+		Clock clock = new Clock(1, "Seiko");
+		s.save( clock );
+		clock = new Clock( 2, "Festina");
+		s.save( clock );
+		Book book = new Book(1, "La chute de la petite reine a travers les yeux de Festina", "La chute de la petite reine a travers les yeux de Festina, blahblah");
+		s.save(book);
+		book = new Book(2, "La gloire de mon père", "Les deboires de mon père en vélo");
+		s.save(book);
+		tx.commit();
+		s.clear();
+		tx = s.beginTransaction();
+		QueryParser parser = new QueryParser("title", new StopAnalyzer() );
+
+		Query query = parser.parse( "summary:noword" );
+		org.hibernate.Query hibQuery = s.createFullTextQuery( query, Clock.class, Book.class );
+		List result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( 0, result.size() );
+
+		query = parser.parse( "summary:Festina Or brand:Seiko" );
+		hibQuery = s.createFullTextQuery( query, Clock.class, Book.class );
+		result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( "Query with explicit class filter", 2, result.size() );
+
+        query = parser.parse( "summary:Festina Or brand:Seiko" );
+		hibQuery = s.createFullTextQuery( query );
+		result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( "Query with no class filter", 2, result.size() );
+        for (Object element : result) {
+			assertTrue( Hibernate.isInitialized( element ) );
+			s.delete( element );
+		}
+		for (Object element : s.createQuery( "from java.lang.Object" ).list() ) s.delete( element );
+		tx.commit();
+		s.close();
+	}
+
+	public void testFirstMax() throws Exception {
+		FullTextSession s = Search.createFullTextSession( openSession() );
+		Transaction tx = s.beginTransaction();
+		Clock clock = new Clock(1, "Seiko");
+		s.save( clock );
+		clock = new Clock( 2, "Festina");
+		s.save( clock );
+		Book book = new Book(1, "La chute de la petite reine a travers les yeux de Festina", "La chute de la petite reine a travers les yeux de Festina, blahblah");
+		s.save(book);
+		book = new Book(2, "La gloire de mon père", "Les deboires de mon père en vélo");
+		s.save(book);
+		tx.commit();
+		s.clear();
+		tx = s.beginTransaction();
+		QueryParser parser = new QueryParser("title", new StopAnalyzer() );
+
+		Query query = parser.parse( "summary:Festina Or brand:Seiko" );
+		org.hibernate.Query hibQuery = s.createFullTextQuery( query, Clock.class, Book.class );
+		hibQuery.setFirstResult( 1 );
+		List result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( "first result no max result", 1, result.size() );
+
+		hibQuery.setFirstResult( 0 );
+		hibQuery.setMaxResults( 1 );
+		result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( "max result set", 1, result.size() );
+
+		hibQuery.setFirstResult( 0 );
+		hibQuery.setMaxResults( 3 );
+		result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( "max result out of limit", 2, result.size() );
+
+		hibQuery.setFirstResult( 2 );
+		hibQuery.setMaxResults( 3 );
+		result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( "first result out of limit", 0, result.size() );
+		
+		for (Object element : s.createQuery( "from java.lang.Object" ).list() ) s.delete( element );
+		tx.commit();
+		s.close();
+	}
+
+	public void testIterator() throws Exception {
+		FullTextSession s = Search.createFullTextSession( openSession() );
+		Transaction tx = s.beginTransaction();
+		Clock clock = new Clock(1, "Seiko");
+		s.save( clock );
+		clock = new Clock( 2, "Festina");
+		s.save( clock );
+		Book book = new Book(1, "La chute de la petite reine a travers les yeux de Festina", "La chute de la petite reine a travers les yeux de Festina, blahblah");
+		s.save(book);
+		book = new Book(2, "La gloire de mon père", "Les deboires de mon père en vélo");
+		s.save(book);
+		tx.commit();//post commit events for lucene
+		s.clear();
+		tx = s.beginTransaction();
+		QueryParser parser = new QueryParser("title", new StopAnalyzer() );
+
+		Query query = parser.parse( "summary:noword" );
+		org.hibernate.Query hibQuery = s.createFullTextQuery( query, Clock.class, Book.class );
+		Iterator result = hibQuery.iterate();
+		assertNotNull( result );
+		assertFalse( result.hasNext() );
+
+		query = parser.parse( "summary:Festina Or brand:Seiko" );
+		hibQuery = s.createFullTextQuery( query, Clock.class, Book.class );
+		result = hibQuery.iterate();
+		assertNotNull( result );
+		int index = 0;
+		while ( result.hasNext() ) {
+			index++;
+			s.delete( result.next() );
+		}
+		assertEquals( 2, index );
+		for (Object element : s.createQuery( "from java.lang.Object" ).list() ) s.delete( element );
+		tx.commit();
+		s.close();
+	}
+
+	public void testScrollableResultSet() throws Exception {
+		FullTextSession s = Search.createFullTextSession( openSession() );
+		Transaction tx = s.beginTransaction();
+		Clock clock = new Clock(1, "Seiko");
+		s.save( clock );
+		clock = new Clock( 2, "Festina");
+		s.save( clock );
+		Book book = new Book(1, "La chute de la petite reine a travers les yeux de Festina", "La chute de la petite reine a travers les yeux de Festina, blahblah");
+		s.save(book);
+		book = new Book(2, "La gloire de mon père", "Les deboires de mon père en vélo");
+		s.save(book);
+		tx.commit();//post commit events for lucene
+		s.clear();
+		tx = s.beginTransaction();
+		QueryParser parser = new QueryParser("title", new StopAnalyzer() );
+
+		Query query = parser.parse( "summary:noword" );
+		org.hibernate.Query hibQuery = s.createFullTextQuery( query, Clock.class, Book.class );
+		ScrollableResults result = hibQuery.scroll();
+		assertNotNull( result );
+		assertEquals(-1, result.getRowNumber() );
+		assertEquals(false, result.next() );
+		result.close();
+
+		query = parser.parse( "summary:Festina Or brand:Seiko" );
+		hibQuery = s.createFullTextQuery( query, Clock.class, Book.class );
+		result = hibQuery.scroll();
+		assertEquals(0, result.getRowNumber() );
+		result.beforeFirst();
+		assertEquals( true, result.next() );
+		assertTrue( result.isFirst() );
+		assertTrue( result.scroll( 1 ) );
+		assertTrue( result.isLast() );
+		assertFalse( result.scroll( 1 ) );
+		result.beforeFirst();
+		while ( result.next() ) {
+			s.delete( result.get()[0] );
+		}
+		for (Object element : s.createQuery( "from java.lang.Object" ).list() ) s.delete( element );
+		tx.commit();
+		s.close();
+	}
+
+	public void testMultipleEntityPerIndex() throws Exception {
+		FullTextSession s = Search.createFullTextSession( openSession() );
+		Transaction tx = s.beginTransaction();
+		Clock clock = new Clock(1, "Seiko");
+		s.save( clock );
+		Book book = new Book(1, "La chute de la petite reine a travers les yeux de Festina", "La chute de la petite reine a travers les yeux de Festina, blahblah");
+		s.save(book);
+		AlternateBook alternateBook = new AlternateBook(1, "La chute de la petite reine a travers les yeux de Festina");
+		s.save(alternateBook);
+		tx.commit();
+		s.clear();
+		tx = s.beginTransaction();
+		QueryParser parser = new QueryParser("title", new StopAnalyzer() );
+
+		Query query = parser.parse( "summary:Festina" );
+		org.hibernate.Query hibQuery = s.createFullTextQuery( query, Clock.class, Book.class );
+		List result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( "Query with explicit class filter", 1, result.size() );
+		
+		query = parser.parse( "summary:Festina" );
+		hibQuery = s.createFullTextQuery( query, Clock.class, Book.class );
+		Iterator it = hibQuery.iterate();
+		assertTrue( it.hasNext() );
+		assertNotNull( it.next() );
+		assertFalse( it.hasNext() );
+
+		query = parser.parse( "summary:Festina" );
+		hibQuery = s.createFullTextQuery( query, Clock.class, Book.class );
+		ScrollableResults sr = hibQuery.scroll();
+		assertTrue( sr.first() );
+		assertNotNull( sr.get() );
+		assertFalse( sr.next() );
+		sr.close();
+
+		query = parser.parse( "summary:Festina OR brand:seiko" );
+		hibQuery = s.createFullTextQuery( query, Clock.class, Book.class );
+		hibQuery.setMaxResults( 2 );
+		result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( "Query with explicit class filter and limit", 2, result.size() );
+
+		query = parser.parse( "summary:Festina" );
+		hibQuery = s.createFullTextQuery( query );
+		result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( "Query with no class filter", 2, result.size() );
+        for (Object element : result) {
+			assertTrue( Hibernate.isInitialized( element ) );
+			s.delete( element );
+		}
+		for (Object element : s.createQuery( "from java.lang.Object" ).list() ) s.delete( element );
+		tx.commit();
+		s.close();
+	}
+
+
+	protected Class[] getMappings() {
+		return new Class[] {
+				Book.class,
+				AlternateBook.class,
+				Clock.class
+		};
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/session/Email.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/session/Email.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/session/Email.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,64 @@
+//$Id: $
+package org.hibernate.search.test.session;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.GeneratedValue;
+
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.DocumentId;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Index;
+
+/**
+ * @author Emmanuel Bernard
+ */
+ at Entity
+ at Indexed
+public class Email {
+	@Id
+	@GeneratedValue
+	@DocumentId
+	private Long id;
+
+	@Field(index = Index.TOKENIZED)
+	private String title;
+
+	@Field(index = Index.TOKENIZED)
+	private String body;
+
+	private String header;
+
+
+	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 getBody() {
+		return body;
+	}
+
+	public void setBody(String body) {
+		this.body = body;
+	}
+
+	public String getHeader() {
+		return header;
+	}
+
+	public void setHeader(String header) {
+		this.header = header;
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/session/MassIndexTest.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/session/MassIndexTest.java	2006-11-23 22:41:27 UTC (rev 10866)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/session/MassIndexTest.java	2006-11-24 00:37:46 UTC (rev 10867)
@@ -0,0 +1,68 @@
+//$Id: $
+package org.hibernate.search.test.session;
+
+import java.util.List;
+
+import org.hibernate.search.test.TestCase;
+import org.hibernate.search.impl.FullTextSessionImpl;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.search.Search;
+import org.hibernate.Transaction;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.analysis.StopAnalyzer;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class MassIndexTest extends TestCase {
+
+	public void testTransactional() throws Exception {
+		FullTextSession s = Search.createFullTextSession( openSession() );
+		Transaction tx = s.beginTransaction();
+		int loop = 4;
+		for (int i = 0 ; i < loop; i++) {
+			Email email = new Email();
+			email.setTitle( "JBoss World Berlin" );
+			email.setBody( "Meet the guys who wrote the software");
+			s.persist( email );
+		}
+		tx.commit();
+		s.close();
+
+		s = new FullTextSessionImpl( openSession() );
+		s.getTransaction().begin();
+		s.connection().createStatement().executeUpdate( "update Email set body='Meet the guys who write the software'");
+		s.getTransaction().commit();
+		s.close();
+
+		s = new FullTextSessionImpl( openSession() );
+		tx = s.beginTransaction();
+		QueryParser parser = new QueryParser("id", new StopAnalyzer() );
+		List result = s.createFullTextQuery( parser.parse( "body:write" ) ).list();
+		assertEquals( 0, result.size() );
+		result = s.createCriteria( Email.class ).list();
+		for (int i = 0 ; i < loop/2 ; i++)
+			s.index( result.get( i ) );
+		tx.commit(); //do the process
+		s.index( result.get(loop/2) ); //do the process out of tx
+		tx = s.beginTransaction();
+		for (int i = loop/2+1 ; i < loop; i++)
+			s.index( result.get( i ) );
+		tx.commit(); //do the process
+		s.close();
+
+		s = new FullTextSessionImpl( openSession() );
+		tx = s.beginTransaction();
+		result = s.createFullTextQuery( parser.parse( "body:write" ) ).list();
+		assertEquals( loop, result.size() );
+		for (Object o : result) s.delete( o );
+		tx.commit();
+		s.close();
+	}
+
+	protected Class[] getMappings() {
+		return new Class[] {
+				Email.class
+		};
+	}
+}




More information about the hibernate-commits mailing list