Thursday, June 18, 2009

Compass (http://www.compass-project.org/) makes integrating search functionality into your website a breeze, only if you are building it in Java though. I have been experimenting a lot with other languages like php, python, rails but always turn back to java and the reason has mostly been the compelling library support and more-recently Compass. Maybe it is my naivety, but I couldn't find anything half as interesting anywhere else.

So, back to compass. Lucene is a well known search tool offered in Java. But was a bit difficult to setup and use, until someone built a user-friendly layer on top. Don't get me wrong, lucene is pretty good in itself and can be setup if you are ready to spend a week (two weeks if you want to optimize). However, that involves writing a lot of code, which we no longer have to do in compass. For e.g. when using Date queries, in Lucene you have to convert dates into lexicographic order (YYYY-MM-DD) before you can run span queries on it. But compass has that functionality built in using a DateConverter. And then compass integrates much more cleanly with hibernate than Lucene used to. So much for the rant. Lets see it in action.

We need two configuration file, assuming we have the data model ready.
compass.cfg.xml - Compass specific configuration


<compass-core-configuration>
<compass>
<setting name="compass.engine.connection">indexes</setting>
<mapping resource="search/model.cpm.xml">
</mapping>
</compass>
</compass-core-configuration>


model.cpm.xml - Mapping from Models to Indexes

<compass-core-mapping package="com.model">
<class name="User" alias="user">
<id name="id">
<dynamic-meta-data name="text" converter="ognl">toString()</dynamic-meta-data>
<constant>
<meta-data>type</meta-data>
<meta-data-value>user</meta-data-value>
</constant>
<component name="profile">
<property name="userName">
<property name="email">
</property>
</property>
</component></id></class></compass-core-mapping>


Other than this, we need to provide a add a HibernateGPS device as an entity listener. This listens to any updates done on the entity and writes these out to the database. I use spring, so all this config goes into my application context xml.

<bean id="hibernateGpsDevice" class="org.compass.gps.device.hibernate.HibernateGpsDevice">
<property name="name"><value>hibernateDevice</value></property>
<property name="sessionFactory"><ref local="sessionFactory"></ref>
<property name="nativeExtractor"><bean class="org.compass.spring.device.hibernate.SpringNativeHibernateExtractor"></bean>
</property>



<bean id="compassGps" class="org.compass.gps.impl.SingleCompassGps" method="start">
<property name="compass"><ref bean="compass"></ref>
<property name="gpsDevices">
<list>
<ref bean="hibernateGpsDevice">
</ref>
</list>
</property>
</property></bean></property></bean><


Once that is done, we can create/re-create search index by calling compassGps.index(). Now just need to create a Compass object and our search is in order.

<bean id="compass" class="org.compass.spring.LocalCompassBean">
<property name="configLocation" value="classpath:search/compass.cfg.xml">
<property name="transactionManager" ref="transactionManager">
</property>
</property></bean>


And now do the actual search, with pagination et. all.

final CompassSearchHelper compassHelper = new CompassSearchHelper(this.compass, pageSize);
return new SearchResults(compassHelper.search(new CompassSearchCommand("email: test@test.com", page)));


And we are done. Happy searching.