In this article, I will describe the steps required to configure Hibernate Search with JBoss Seam.

First thing we need to do is add the Hibernate Search settings to the persistence.xml file as follows.


<!-- use a file system based index -->
<property name="hibernate.search.default.directory_provider"
value="org.hibernate.search.store.FSDirectoryProvider" />

<!-- directory where the indexes will be stored -->
<property name="hibernate.search.default.indexBase"
value="[/path/to/your/location/directory]" />

<property name="hibernate.ejb.event.post-insert"
value="org.hibernate.search.event.FullTextIndexEventListener" />

<property name="hibernate.ejb.event.post-update"
value="org.hibernate.search.event.FullTextIndexEventListener" />

<property name="hibernate.ejb.event.post-delete"
value="org.hibernate.search.event.FullTextIndexEventListener" />


This sets up the directory provider class, the location of the indexes and the listeners for handling the update to the indexes on insert, update and delete operations.

Next, we need to add in the hibernate-search.jar, hibernate-commons-annotations.jar and lucene-core.jar files into the lib directory in your ear file.

OK, now we need to tell Hibernate Search which objects to index and which attributes we are going to be interested in.


@Entity
@Name("product")
@Indexed
public class Product implements Serializable {
static final long serialVersionUID = 1l;

@Id @GeneratedValue @DocumentId
private Long id;

@NotNull
@Field(index = Index.TOKENIZED)
private String name;

@NotNull
@Field(index = Index.TOKENIZED)
private String description;

// getters and setters
}




What we have here is an @Indexed annotation which tells Hibernate Search that this persistent class is indexable. From there we have a @DocumentId which indicates that this attribute is the ID of the class and is to be stored and not indexed. From there on we have two attributes name and description. Both of these attributes are annotated with @Field and in this example both are tokenized and this indicates to Hibernate Search to allow the analyzer to process the property. Other allowed values here are Index.NO (don't analyze), Index.UN_TOKENIZED (no pre-processing from the analyzer), Index.NO_NORM (don't store the normalization data).

Now we have everything indexed in Lucene, we need a way of searching it. For this we will have a SearchManager class.


@Name("SearchManager")
public class SearchManager {
@In
private FullTextEntityManager entityManager;

private String searchPattern;

// getters and setters for searchPattern

public List getResults() {
Map boostFields = new HashMap(2);
// increase the importance of the name field
// over the other product fields
boostFields.put("name", 4f);

String[] productFields = {"name", "description"};

QueryParser parser = new MultiFieldQueryParser(productFields,
new StandardAnalyzer(), boostFields);
parser.setAllowLeadingWildcard(true);

Query luceneQuery;

try {
luceneQuery = parser.parse(searchPattern);
} catch (ParseException pe) {
log.error("found a problem in search", pe);
return null;
}

// extract the products
List products =
entityManager.createFullTextQuery(luceneQuery, Product.class).
setMaxResults(20).getResultList();

return products;
}
}


OK, now let's have a search.xhtml file to display the results. Below is a snippet from this file


<rich:dataGrid value="#{SearchManager.results}" var="product">
[ loop over the values ]

</rich:dataGrid>



Then add an entry into pages.xml for search.xhtml


<page view-id="/search.xhtml">

<param name="searchPattern"
value="#{SearchManager.searchPattern}"/>

</page>


One more step. Let's add a search box to the menu. If you have followed the standard layout for a Seam project, then you will have a menu.xhtml file within the layout of your view. If not, then just place the following in a logical place in your application.


<h:form id="search_form">
<h:inputText id="searchPattern" required="true"
value="#{SearchManager.searchPattern}" />
<h:commandButton action="/search.xhtml"
value="search"></h:commandButton>
</h:form>


And from here, you should have a working search of your mapped entities. Which is great. But what happens if you have an attribute on this entity that dictates the visibility of the entity in the web site? Well in this instance, you need to use a Filter. These filters extend the org.apache.lucene.search.Filter class.

Imagine we have a status attribute on the Product class with three values; "L" for live, "D" for deleted and "P" for pending. Obviously in our customer facing search, we only want the live products to be returned. So our filter will look like:


public class LiveProductFilter extends Filter {
private static final long serialVersionUID = 1l;

public BitSet bits(IndexReader reader) throws IOException {
BitSet bitSet = new BitSet( reader.maxDoc() );
TermDocs termDocs = reader.termDocs( new Term("status", "L") );
while ( termDocs.next() ) {
bitSet.set( termDocs.doc() );
}
return bitSet;
}
}


In order to indicate to Hibernate Search that we wish to apply the filter to the Product class, we add the following annotation to the class.


@FullTextFilterDefs ( {
@FullTextFilterDef(name="liveProduct",
impl = LiveProductFilter.class, cache=false)
})


And that is it.

For further information, check out the Hibernate Search documentation.