Wednesday, March 12, 2008

Search with Spring Hibernate Lucene and Aspect Oriented Programming in action

I've read a lot about AOP and what it's used for. It is useful for logging, tracing, declarative transactions management and even caching. Great ! I'm going to share with you yet another AOP use case. BTW, I found it really interesting. So, the story.

I have some entity with with field "content" which is HTML text. The number of entries is large and keep growing. It is natural that I want to find some entry which contain some text. So, I need search. Since text is in HTML format, database full text search is not the best solution possible. I've decided to try Lucene. It is a "high-performance, full-featured text search engine library". To use Lucene I need to crate an index, add my objects into it and then I will able to query index to get search results.

Since I'm using Spring, I was looking for Lucene-Spring integration. Bingo ! It is available as one of Spring modules project. This integration has two aspects - one for index management and another for performing actual search. Complete configuration :

<bean id="fsDirectory"
class="org.springmodules.lucene.index.support.FSDirectoryFactoryBean">
<property name="create" value="true"/>
<property name="location" value="file:///C:/temp/index" />
</bean>

<alias name="fsDirectory" alias="indexDirectory"/>

<bean
id="indexFactory"
class="org.springmodules.lucene.index.support.SimpleIndexFactoryBean">
<property name="create" value="true"/>
<property name="directory">
<ref bean="indexDirectory" />
</property>
<property name="analyzer">
<bean class="org.apache.lucene.analysis.standard.StandardAnalyzer"/>
</property>
</bean>

<bean id="indexAccessor"
class="com.mycoolcompany.app.search.LuceneIndexerService">
<property name="indexFactory">
<ref local="indexFactory" />
</property>
</bean>

<bean id="searcherFactory"
class="org.springmodules.lucene.search.factory.SimpleSearcherFactory">
<property name="directory">
<ref bean="indexDirectory" />
</property>
</bean>

<bean id="searchService"
class="com.mycoolcompany.app.search.LuceneSearchService">
<property name="searcherFactory">
<ref local="searcherFactory" />
</property>
<property name="analyzer">
<bean class="org.apache.lucene.analysis.standard.StandardAnalyzer" />
</property>
</bean>

I can pre-populate index by querying database and adding objects to index. But what to do when new entry has been added ? How to keep index up to date ? After some Googling I found solution with custom hibernate interceptor. It looks like this :

import java.io.Serializable;
import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;

public class CustomHibernateInterceptor extends EmptyInterceptor
{
private static final long serialVersionUID = -1214372449390884955L;

//injected by spring
private LuceneIndexerService indexAccessor;
public LuceneIndexerService getIndexAccessor() {
return indexAccessor;
}
public void setIndexAccessor(LuceneIndexerService indexAccessor) {
this.indexAccessor = indexAccessor;
}

public boolean onSave(
Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types)
{
if (entity instanceof Entry)
{
//adding to index
indexAccessor.add((Entry)entity);
}
return super.onSave(entity, id, state, propertyNames, types);
}
}
Is it something wrong with this solution ? I guess no, it's working. But I have and idea to use AOP here. I guess, adding to index and persisting object to database at the same time is some king of cross-cutting concern. I'm using a Spring-Hibernate DAO for CRUD operations. My idea to resolve this issue is in using aspect. Aspect has a pointcut defined to intercept execution of my dao method addEntry(Entry entry) and "after" advice - this is the place where we can add object to index, after object has been persisted. Please notice that search service is injected into aspect :) All this stuff is very abstract but is pretty interesting to digg. Here is the source code of aspect :
@Aspect
public class IndexForSearchAspect
{
public IndexForSearchAspect()
{
super();
}
//will be injected
private LuceneIndexerService indexAccessor;
public void setIndexAccessor(LuceneIndexerService indexAccessor)
{
this.indexAccessor = indexAccessor;
}
//defining pointcut
@Pointcut("execution (* ....dao.BlogEntryDao.addEntry(....model.Entry))")
public void saveEntryToDB() {}

//add object to index after it has been persisted
@After("saveEntryToDB()")
public void addEntryToLuceneIndex(JoinPoint joinpoint)
{
//getting argument of dao.addEntry()
Entry entry = (Entry) joinpoint.getArgs()[0];
// adding to index
indexAccessor.add(entry);
}
}
Spring configuration for aspect :

<aop:aspectj-autoproxy/>
<bean id="searchindexAspect"
class="com.mycoolcompany.app.search.IndexForSearchAspect">
<!--indexer service will be injected -->
<property name="indexAccessor">
<ref local="indexAccessor"/>
</property>
</bean>

I know, I've missed a lot of details here, so this post is something like show-case, not cookbook :) Any opinions are welcome !

Tuesday, March 04, 2008

Even church using JSF !

I had a very interesting blog visitor which came from Google search. I'm not wondering a lot when I have visitors from high-tech companies or financial institutions, but church is something new and unexpected ! So, it is "The Church of Jesus Christ of Latter-day Saints". Take a look at Google Analytics screen :


I don't know how JSF is used there, but they looked for "jsf page redirect params".
Halleluiah ! :)