So you want to search for your custom JPA entities in Liferay? Quite some documentation is already available but it is somewhat scattered over several blogposts and articles. With this blogpost I would like to give you a summary of everything you need to do, or more precisely everything we did, to make custom JPA entities searchable in the standard Liferay Search Portlet.
The requirement
One of our customers wanted the search results to not only contain the default Liferay objects like articles, documents, etc... But also some of his own custom domain objects. We had created a custom lightweight Support Ticket framework with a set of limited functionalities. It allowed a user to create a ticket and post comments to it. The additional requirement was that a user had to be able to also search for these tickets based on title, description and... oh yeah the comments too. In this blog I will use a very simplified version of this domain that shows all the steps necessary to make a JPA entity searchable. The domain only has 1 JPA entity, SupportTicket, together with a repository and service. There is also a simple portlet that can be used to create and show instances of this entity. So let's go on a journey, a quest you might even call it, to enable the search functionality for your custom JPA entities. Eventually you will get to the end point where you will use the Liferay Search Portlet to search and find your own JPA entities. You can also create your own custom search (taking customization to the max!) but that is outside the scope of this blog post. In the resources section however you can find links for more information on this matter. In a vanilla Liferay 6.2 the result should look like this:
Enter the Indexer
Liferay’s search & indexing functionality is provided by Apache Lucene, a Java search framework. It converts searchable entities into documents. Documents are custom objects that correspond to searchable entities. Usually performing full text searches against an index is much faster than searching for entities in a database. You can find more information about Lucene on its own website. To start you will need to create an indexer class. This class will hold the information required to transform your JPA entity into a Lucene Document. Next you will need to register this Indexer in the portlet so that the Liferay framework knows about it and can use it when necessary.
Creating the Indexer
I already made clear that the Indexer class is responsible for creating the Lucene documents for your JPA entity. It is important to know that this is where you can decide what fields of your entity will get indexed in order to be searchable. You can also specify if this needs to be a term or a phrase. And of course there are some other settings you can implement such as the name of the associated portlet, permissions, ... Have a look at the Sources for blog posts with more information. Liferay provides you with a default implementation named BaseIndexer as an easy start. This abstract class should be extended and the required methods implemented. Enough of the theoretical stuff, let's get started. As soon as you extend the BaseIndexer, you will need to overwrite the following methods. I'm not going to go into the details of all the implementations, I refer you to the Github project. Mostly the implementation will depend on your own requirements and domain.
- doDelete: delete the document that corresponds to the object parameter
- doGetDocument: specify which fields the Lucene Index will contain
- doGetSummary: used to show a summary of the document in the search results
- doReindex (3 times): will be called when the index is updated
- getPortletId (2 times): to be able to narrow searches for documents created in a certain portlet instance (hence it needs to be the full portlet Id)
- getClassNames: the FQCN of the entities this indexer can be used for
Something important to notice in this class is the difference between document.addKeyword and document.addText in the doGetDocument method.
- addKeyword: adds a single-valued field, with only one term
- addText: adds a multi-valued field, with multiple terms separated by a white space
The doGetDocument method is the most important piece of code in the TicketIndexer. In this method an incoming Object is transformed into an outgoing (Lucene) Document. The object is of type Ticket as the Indexer has been registered to be used for objects of that type. This registration occurs in the getClassNames method. A new Document object holding the necessary searchable fields can be created from this Ticket. Notice that all the Keyword/Text fields are already predefined in Liferay so you can use these constants. This is quite handy but you can always provide your own names.
@Override protected Document doGetDocument(Object obj) throws Exception { Ticket ticket = (Ticket) obj; List<TicketComment> comments = BeanLocator.getBean(TicketCommentService.class).getComments(ticket); Document document = new DocumentImpl();

