Solr Indexing of ORM Entities: SolrEntity

ColdFusion 9 introduced two new features, Hibernate based ORM and Solr full text search indexing, however using the two together never seemed as easy as it could be. In the Java world you have Hibernate Search or the SolrJ client, both of which allow you to easily include an entity in a Lucene/Solr search index via simple annotations. Taking those projects as inspiration I came up with SolrEntity, a base component that implements ColdFusion ORM event handler methods to manage indexing of an entity via simple annotations (custom metadata attributes).

Here is a quick sample of SolrEntity in action. Say I have the following two components in my (very simple) ORM based blog application.

Entry.cfc:

component persistent="true" table="entry" {
property name="entryid" filedtype="id" generator="native";
property name="author" fieldtype="many-to-one" fkcolumn="authorid" cfc="Author";
property name="title";
property name="content";

function setAuthor(author) {
if ( !IsNull( arguments.author) ) {
variables.author = arguments.author;
if (!arguments.author.hasEntry(this)) {
if (!IsNull(arguments.author.getEntries())){
ArrayAppend( arguments.author.getEntries(), this );
} else {
arguments.author.setEntries([this]);
}
}
}
}
}
Author.cfc:
component persistent="true" table="author" {

property name="authorid" fieldtype="id" generator="native";
property name="name";
property name="entries" type="array" inverse="true" fieldtype="one-to-many" cfc="Entry" fkcolumn="authorid" lazy="true" singularname="Entry";

function addEntry(entry) {
entry.setAuthor(this);
}

}

Now lets say we want to index Entries in a Solr index for full text search. First, I'd create a new Solr collection for my blog in the ColdFusion administrator. For this example will assume the index is named "blog". Next I make sure ORM event handling is enabled for my applicaiton.

Applicaiton.cfc:

this.ormSettings = { eventhandling = true };

Next I'll modify my Entry component to extend the base SolrEntity.cfc and annotate it so that certain properties are indexed in our new collection.

Entry.cfc:

component persistent="true" table="entry" extends="SolrEntity" solrcollection="blog" {

property name="entryid" filedtype="id" generator="native" solrfield="key";
property name="author" fieldtype="many-to-one" fkcolumn="authorid" cfc="Author";
property name="title" solrfield="title";
property name="content" solrfield="body";

function setAuthor(author) {
if ( !IsNull( arguments.author) ) {
variables.author = arguments.author;
if (!arguments.author.hasEntry(this)) {
if (!IsNull(arguments.author.getEntries())){
ArrayAppend( arguments.author.getEntries(), this );
} else {
arguments.author.setEntries([this]);
}
}
}
}

}
We now see that the component extends SolrEntity and I've identified the collection to use via the solrcollection custom attribute. Furthermore I've added solrfield custom attributes to the id, title, and content properties to map them to the key, title, and body fields in the Solr collection. All of the standard fields exposed by cfindex such as body, category, categoryTree, custom 1, etc. can be mapped via the solrfield annotation. But what about the author? In this case author is represented by a many-to-one relationship to the Author entity. Further complicating the matter, the standard cfindex tag does not expose author field in the Solr index. Working around both of these issues is relatively straight forward with SolrEntity. First we will create a no argument function in the entity component to get the Author name.
function getAuthorName() {
return this.getAuthor().getName();
}
Then we simply annotate this with the solrfield custom attribute, just like we did with property.
function getAuthorName() solrfield="author" {
return this.getAuthor().getName();
}
Now whenever we create or update an Entry the author's name will be stored in the author filed of the Solr index.

SolrEntity is early in development, but you can get the code along with a very simple example application on github. I should also note, it only works against CF 9.0.1 based Solr collections. As always, feedback is welcome. One thing I'm curious about is the current implementation as a base component. I did this because I assumed you would likely only have one, or at most a few, components per application mapped to a Solr index. However, it may make more sense to move this logic into an event handler component. I'm just not sure of the better approach at this point.

Comments
Henry's Gravatar Interesting, but is it possible to make this work without "extend"? My ORM entities already extending something and it'll get messy.
# Posted By Henry | 11/30/10 1:57 PM
Nathan MIsche's Gravatar Yes, you could do this without extending a base component if you move the logic into an ORM event handler.
# Posted By Nathan MIsche | 12/8/10 8:56 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.8.001.