Home Liferay Implementing Lucene Search In Liferay Custom Portlet
formats

Implementing Lucene Search In Liferay Custom Portlet

Published on April 24, 2014, by in Liferay.

Hello all of you. Recently, I have started learning Liferay. Liferay Portal is a free and open source enterprise portal project written in Java and distributed under the GNU Lesser General Public License and optional commercial license. The Liferay project additionally supports Liferay Social Office, Liferay Sync, Liferay AlloyUI, Liferay enterprise Connectivity Apps and the Liferay Marketplace. It is primarily used to power corporate intranets and extranets.

In Liferay, many framework has been used like Struts, Eh-Cache, Hibernate, Spring, Solar, Lucene. We can make our custom portlet by using any of the above framework.

Now, we will see how we can implement  Lucene Search in our custom portlet.

Prerequisite :-1) Developing Custom portlet in Liferay 6.2.

                            2) Apache Lucene.

For basic terms and definition of Lucene you can go through following link :-

http://lucene.apache.org/core/3_0_3/queryparsersyntax.html.

You can also use Luke – Lucene Index Toolbox for viewing the document which has been created by Liferay.

http://www.getopt.org/luke/.

Indexing and Search in Liferay

Liferay uses indexing and search throughout the portal. In fact, we could argue that a portal isn’t worth much without indexing and search, because after filling it with content, you need a way of finding that content, right? Thankfully, Liferay gives you an easy way to do that, by providing a robust  API to use to index data and search for it.

Liferay uses Lucene by default under the hood. This is a feature-rich search engine provided by Apache that is so widely used that most likely you’re not surprised that Liferay uses it. Lucene was originally written in Java but has been ported to .Net, C, Python, Perl, Delphi, Ruby,  LISP ,  PHP , Objective- C , and probably grandma’s home-cooked programming language to go, by now. If you need to do search, and you’re not Google, you can’t go wrong by using Lucene.

It’s kind of amazing to find such widespread support for something like Lucene, but there’s a good reason: its  API is awesome. In fact, it’s so awesome that Liferay’s search  API is pretty much just a wrapper around it. When you find something this awesome, why fight it?

But you aren’t tied to Lucene if for some incredible reason you don’t want to use it. You can still write your portlet to Liferay’s search API , and if you’re an enterprise customer using one of Liferay’s search plugins for commercial search servers, it’ll work. You still get great search performance and features with the same  API , no matter what the underlying implementation is.

I think the reason everybody likes this particular  API is that it lets you search anything by abstracting everything into only two components:  Document s and  Field s. You translate the data (or the system translates well-known formats, such as  PDF s, Open Document files, or Word documents) into a  Document with  Field s on it, and then you pass that  Document on to the indexer, which happily churns through it and makes the data searchable.

This also helps to avoid weird database queries. Instead of searching the database, you can search the index, and then retrieve the proper entity through a  Document -to- entity conversion (see – figure)

document

Similarly, you do the same thing in reverse to do the indexing. At the same time you save the entity into the database, you can convert it into a  Document and then kick off the indexer to get its content into the index. And I’m betting you can  already guess how this happens on a separate process outside of the browser request and response. Under the hood, again, the message bus is used to make it happen. But this time, you don’t have to do  any configuration or implement any listeners—it’s all done for you, by creating an  indexer. Let’s get to the code to see how this is done.

Indexing your data

To index data, you need to implement an interface for translating entities to Document s and back. The first step is to declare an indexer in liferay-portlet.xml with a single line in the  <portlet> block:

                                          <indexer-class>

                                                   com.portlet.sample.util.SampleIndexer

                                       </indexer-class>

 

After you’ve declared it, you can implement the indexer. This class needs to extend Liferay’s  BaseIndexer class; and when you do that, you’ll find that there are five methods you need to implement, described in following image.

 image

 

Let’s start with Demo:-

 

Steps:-

1) Create your custom portlet.

 eclipse

 

 

First lets create service.xml for our project as we are going to perform search on custom portlet using custom object.

 

service.xml

 

<?xml version=”1.0″ encoding=”UTF-8″?>

<!DOCTYPE service-builder PUBLIC “-//Liferay//DTD Service Builder 6.1.0//EN” “http://www.liferay.com/dtd/liferay-service-builder_6_1_0.dtd”>

<service-builder package-path=”com.portlet.sample”>

<author>Scalsysu5</author>

<namespace>Sample</namespace>

<entity name=”SampleEntry” uuid=”true” local-service=”true”

remote-service=”true”>

<!– PK fields –>

<column name=”entryId” type=”long” primary=”true”></column>

<column name=”companyId” type=”long”></column>

<column name=”groupId” type=”long”></column>

<column name=”userId” type=”long”></column>

<column name=”userName” type=”String”></column>

<column name=”title” type=”String”></column>

<column name=”content” type=”String”></column>

<column name=”createDate” type=”Date”></column>

<column name=”modifiedDate” type=”Date”></column>

<column name=”status” type=”boolean”></column>

</entity>

</service-builder>

 

After creating service.xml just run :- ant build-service

Now services classes and interfaces would be created by Liferay in your project.

We will putting indexer on our custom object SampleEntry.

Now, we will create our indexer class following is the snapshot of my project explorer.

 

project Explorer

 

Now, we will make entry of our Indexer class in liferay-portlet.xml.

liferay-portlet.xml file

<?xml version=”1.0″?>

<!DOCTYPE liferay-portlet-app PUBLIC “-//Liferay//DTD Portlet Application 6.2.0//EN” “http://www.liferay.com/dtd/liferay-portlet-app_6_2_0.dtd”>

<liferay-portlet-app>

<portlet>

<portlet-name>Test1</portlet-name>

<icon>/icon.png</icon>

<indexer-class>com.portlet.sample.util.SampleIndexer</indexer-class>

<open-search-class>com.portlet.sample.util.SampleOpenSearchImpl</open-search-class>

<portlet-url-class>com.liferay.portal.apache.bridges.struts.LiferayStrutsPortletURLImpl</portlet-url-class>

<use-default-template>true</use-default-template>

<instanceable>true</instanceable>

<header-portlet-css>/css/main.css</header-portlet-css>

<footer-portlet-javascript>/js/main.js</footer-portlet-javascript>

<css-class-wrapper>Test1-portlet</css-class-wrapper>

</portlet>

<role-mapper>

<role-name>administrator</role-name>

<role-link>Administrator</role-link>

</role-mapper>

<role-mapper>

<role-name>guest</role-name>

<role-link>Guest</role-link>

</role-mapper>

<role-mapper>

<role-name>power-user</role-name>

<role-link>Power User</role-link>

</role-mapper>

<role-mapper>

<role-name>user</role-name>

<role-link>User</role-link>

</role-mapper>

</liferay-portlet-app>

SampleIndexer.class

 

package com.portlet.sample.util;

 

import java.util.ArrayList;

import java.util.Collection;

import java.util.Date;

import java.util.List;

import java.util.Locale;

 

import javax.portlet.PortletURL;

 

import com.liferay.portal.kernel.search.BaseIndexer;

import com.liferay.portal.kernel.search.BooleanQuery;

import com.liferay.portal.kernel.search.Document;

import com.liferay.portal.kernel.search.DocumentImpl;

import com.liferay.portal.kernel.search.Field;

import com.liferay.portal.kernel.search.Indexer;

import com.liferay.portal.kernel.search.SearchContext;

import com.liferay.portal.kernel.search.SearchEngineUtil;

import com.liferay.portal.kernel.search.Summary;

import com.liferay.portal.kernel.util.GetterUtil;

import com.liferay.portal.kernel.util.StringUtil;

import com.liferay.portal.kernel.util.Validator;

import com.liferay.portlet.expando.model.ExpandoBridge;

import com.liferay.portlet.expando.util.ExpandoBridgeIndexerUtil;

import com.portlet.sample.model.SampleEntry;

import com.portlet.sample.service.SampleEntryLocalServiceUtil;

 

public class SampleIndexer extends BaseIndexer {

public static final String[] CLASS_NAMES = {SampleEntry.class.getName()};

public static final String PORTLET_ID = “Test1”;

@Override

public String[] getClassNames() {

// TODO Auto-generated method stub

System.out.println(“getClassNames calling “);

return CLASS_NAMES;

}

 

@Override

public String getPortletId() {

// TODO Auto-generated method stub

return PORTLET_ID;

}

 

@Override

protected void doDelete(Object obj) throws Exception {

// TODO Auto-generated method stub

SampleEntry entry = (SampleEntry)obj;

Document document = new DocumentImpl();

document.addUID(PORTLET_ID, entry.getEntryId());

SearchEngineUtil.deleteDocument(entry.getCompanyId(), document.get(Field.UID));

}

 

@Override

protected Document doGetDocument(Object obj) throws Exception {

// TODO Auto-generated method stub

System.out.println(“doGetDocument calling in SampleIndexer class “);

SampleEntry entry = (SampleEntry)obj;

long companyId = entry.getCompanyId();

long groupId = getParentGroupId(entry.getGroupId());

long scopeGroupId = entry.getGroupId();

long userId = entry.getUserId();

String userName = entry.getUserName();

long entryId = entry.getEntryId();

String title = entry.getTitle();

String content = entry.getContent();

Date modifiedDate = entry.getModifiedDate();

ExpandoBridge expandoBridge = entry.getExpandoBridge();

Document document = new DocumentImpl();

document.addUID(PORTLET_ID, entryId);

document.addModifiedDate(modifiedDate);

document.addKeyword(Field.COMPANY_ID, companyId);

document.addKeyword(Field.PORTLET_ID, PORTLET_ID);

document.addKeyword(Field.GROUP_ID, groupId);

document.addKeyword(Field.SCOPE_GROUP_ID, scopeGroupId);

document.addKeyword(Field.USER_ID, userId);

document.addText(Field.USER_NAME, userName);

document.addText(Field.TITLE, title);

document.addText(Field.CONTENT, content);

document.addKeyword(Field.ENTRY_CLASS_NAME, SampleEntry.class.getName());

document.addKeyword(Field.ENTRY_CLASS_PK, entryId);

return document;

}

@Override

protected Summary doGetSummary(Document document, Locale locale,

String snippet, PortletURL portletURL) throws Exception {

// TODO Auto-generated method stub

System.out.println(“getSummary calling “);

String title = document.get(Field.TITLE);

String content = snippet;

if (Validator.isNull(snippet)) {

content = StringUtil.shorten(document.get(Field.CONTENT), 200);

}

String entryId = document.get(Field.ENTRY_CLASS_PK);

portletURL.setParameter(“_spage”, “/portlet_action/test/addView”);

portletURL.setParameter(“urlType”, “Action”);

portletURL.setParameter(“entryId”, entryId);

return new Summary(title, content, portletURL);

}

 

@Override

protected void doReindex(Object obj) throws Exception {

// TODO Auto-generated method stub

System.out.println(“doReindex calling “);

SampleEntry entry = (SampleEntry)obj;

System.out.println(” document creation here”);

Document document = getDocument(entry);

System.out.println(“document is === “+document);

SearchEngineUtil.updateDocument(entry.getCompanyId(), document);

System.out.println(“updateDocument is === “+document);

}

 

@Override

protected void doReindex(String className, long classPK) throws Exception {

// TODO Auto-generated method stub

System.out.println(“doReindex calling “);

SampleEntry entry = SampleEntryLocalServiceUtil.getSampleEntry((int) classPK);

doReindex(entry);

}

 

@Override

protected void doReindex(String[] ids) throws Exception {

// TODO Auto-generated method stub

System.out.println(“doReindex calling of string “);

long companyId = GetterUtil.getLong(ids[0]);

reindexEntries(companyId);

}

protected void reindexEntries(long companyId) throws Exception {

int count = SampleEntryLocalServiceUtil.getSampleEntriesCount();

int pages = count / Indexer.DEFAULT_INTERVAL;

for (int i = 0; i <= pages; i++) {

int start = (i * Indexer.DEFAULT_INTERVAL);

int end = start + Indexer.DEFAULT_INTERVAL;

 

reindexEntries(companyId, start, end);

}

}

protected void reindexEntries(long companyId, int start, int end)

throws Exception {

List<SampleEntry> entries = SampleEntryLocalServiceUtil

.getSampleEntries(0,

SampleEntryLocalServiceUtil.getSampleEntriesCount());

if (entries.isEmpty()) {

return;

}

Collection<Document> documents = new ArrayList<Document>();

for (SampleEntry entry : entries) {

Document document = getDocument(entry);

documents.add(document);

}

SearchEngineUtil.updateDocuments(companyId, documents);

}

@Override

protected String getPortletId(SearchContext searchContext) {

// TODO Auto-generated method stub

System.out.println(“getPortletId calling “);

return PORTLET_ID;

}

}

Getting Summary from Document:-

 

protected Summary doGetSummary(Document document, Locale locale,

String snippet, PortletURL portletURL) throws Exception {

// TODO Auto-generated method stub

System.out.println(“getSummary calling “);

String title = document.get(Field.TITLE);

String content = snippet;

if (Validator.isNull(snippet)) {

content = StringUtil.shorten(document.get(Field.CONTENT), 200);

}

String entryId = document.get(Field.ENTRY_CLASS_PK);

portletURL.setParameter(“_spage”, “/portlet_action/test/addView”);

portletURL.setParameter(“urlType”, “Action”);

portletURL.setParameter(“entryId”, entryId);

return new Summary(title, content, portletURL);

}

At the time of calling document.getSummary() it will return above Summary object.

Delete document from Indexer.

@Override

protected void doDelete(Object obj) throws Exception {

// TODO Auto-generated method stub

SampleEntry entry = (SampleEntry)obj;

Document document = new DocumentImpl();

document.addUID(PORTLET_ID, entry.getEntryId());

SearchEngineUtil.deleteDocument(entry.getCompanyId(), document.get(Field.UID));

}

We are deleting document by using company id and document UID which we have put at the time of creating Document.

Converting an entity into a document  object.

 

@Override

protected Document doGetDocument(Object obj) throws Exception {

// TODO Auto-generated method stub

System.out.println(“doGetDocument calling in SampleIndexer class “);

SampleEntry entry = (SampleEntry)obj;

long companyId = entry.getCompanyId();

long groupId = getParentGroupId(entry.getGroupId());

long scopeGroupId = entry.getGroupId();

long userId = entry.getUserId();

String userName = entry.getUserName();

long entryId = entry.getEntryId();

String title = entry.getTitle();

String content = entry.getContent();

Date modifiedDate = entry.getModifiedDate();

ExpandoBridge expandoBridge = entry.getExpandoBridge();

Document document = new DocumentImpl();

document.addUID(PORTLET_ID, entryId);

document.addModifiedDate(modifiedDate);

document.addKeyword(Field.COMPANY_ID, companyId);

document.addKeyword(Field.PORTLET_ID, PORTLET_ID);

document.addKeyword(Field.GROUP_ID, groupId);

document.addKeyword(Field.SCOPE_GROUP_ID, scopeGroupId);

document.addKeyword(Field.USER_ID, userId);

document.addText(Field.USER_NAME, userName);

document.addText(Field.TITLE, title);

document.addText(Field.CONTENT, content);

document.addKeyword(Field.ENTRY_CLASS_NAME, SampleEntry.class.getName());

document.addKeyword(Field.ENTRY_CLASS_PK, entryId);

return document;

}

Above method is called when we are going to index our entity. In above method we are just putting the fields which is require for our search criteria. I had given the field name and its value which will be going to create in Document.

For an example, document.addKeyword(Field.COMPANY_ID, companyId);

Here Field.COMPANY_ID is field name and companyId is value assign to that field.

Liferay has standardized the field names in constants for context within the portal (company  ID ,  group  ID ). So, we can use the standardized field name for our object or otherwise we can also defined our own field name.

 

Reindex of Entities

@Override

protected void doReindex(Object obj) throws Exception {

// TODO Auto-generated method stub

System.out.println(“doReindex calling “);

SampleEntry entry = (SampleEntry)obj;

System.out.println(” document creation here”);

Document document = getDocument(entry);

System.out.println(“document is === “+document);

SearchEngineUtil.updateDocument(entry.getCompanyId(), document);

System.out.println(“updateDocument is === “+document);

}

Here, we are going to reindex the Document when any update is done in our entity object.

 

NOTE :- For Indexing and Reindexing we have to put annotation tag above method where we are adding our Entity in DB like follows.  

@Override

@Indexable(type = IndexableType.REINDEX)

public SampleEntry addEntry(long userId, long groupId, String name,

String title, String content, ServiceContext serviceContext)

throws PortalException, SystemException {

Date now = new Date();

User user = userPersistence.findByPrimaryKey(userId);

long entryId = counterLocalService.increment();

SampleEntry entry = sampleEntryPersistence.create(entryId);

entry.setUuid(serviceContext.getUuid());

entry.setCreateDate(serviceContext.getCreateDate(now));

entry.setModifiedDate(serviceContext.getCreateDate(now));

entry.setTitle(title);

entry.setContent(content);

entry.setUserId(user.getUserId());

entry.setCompanyId(user.getCompanyId());

entry.setGroupId(groupId);

entry.setUserName(name);

entry.setExpandoBridgeAttributes(serviceContext);

sampleEntryPersistence.update(entry);

return entry;

}

 

search.jsp

 

<%@page import=”javax.portlet.PortletURL”%>

<%@ include file=”/html/portlet/test/init.jsp” %>

<%

PortletURL searchURL = renderResponse.createActionURL();

searchURL.setParameter(“_spage”, “/portlet_action/test/search”);

searchURL.setParameter(“search”, “search”);

searchURL.setWindowState(WindowState.NORMAL);

renderRequest.getAttribute(WebKeys.THEME_DISPLAY);

%>

<form  name=”fm1″ action=”<%= searchURL%>” method=”post”>

<table cellpadding=”5″ cellspacing=”10″  style=”border-spacing: 20px 10px; border-collapse: separate; “>

<!–  <tr>

  <td>title :</td>

  <td>content :</td>

 </tr> –>

<tr>

  <td> <input type=”text” name=”<portlet:namespace/>keyword” /></td>

  <%– <td> <input type=”text” name=”<portlet:namespace/>searchContent” /></td> –%>

  </tr>

  <tr>

  <td><input type=”Submit” value=”  Search    ” /></td>

  </tr>

 </table>

 </form>

 

display.jsp

<%@page import=”com.liferay.portal.kernel.util.LocaleUtil”%>

<%@page import=”com.liferay.portal.kernel.search.SearchResultUtil”%>

<%@page import=”com.liferay.portal.kernel.util.StringPool”%>

<%@page import=”com.liferay.portal.kernel.search.Field”%>

<%@page import=”com.liferay.portal.kernel.util.GetterUtil”%>

<%@page import=”com.liferay.portal.kernel.util.MathUtil” %>

<%@page import=”com.liferay.portal.kernel.search.Document”%>

<%@ include file=”/html/portlet/test/init.jsp” %>

 <%– <liferay-ui:search-iterator searchContainer=”<%= searchContainer %>” /> –%>

 <%

  String keyword = request.getAttribute(“keyword”).toString();

   PortletURL portletURL = renderResponse.createRenderURL();

portletURL.setParameter(“struts_action”, “/test/searchPage”);

portletURL.setParameter(“keywords”, keyword);

 %>

 <liferay-portlet:renderURL varImpl=”searchURL”>

<portlet:param name=”struts_action” value=”/portlet_action/test/search” />

<portlet:param name=”search” value=”search”/>

</liferay-portlet:renderURL>

 <aui:form action=”<%= searchURL %>” method=”get” name=”fm”>

<%–

 <liferay-ui:header

backURL=”<%= redirect %>”

title=”search”

/> –%>

 <liferay-ui:search-container

emptyResultsMessage='<%= LanguageUtil.format(pageContext, “no-entries-were-found-that-matched-the-keywords-x”, “<strong>” + “keyword = “+HtmlUtil.escape(keyword) +  “</strong>”) %>’

iteratorURL=”<%= portletURL %>”

>

<%

Indexer indexer = IndexerRegistryUtil.getIndexer(SampleEntry.class);

SearchContext searchContext = SearchContextFactory.getInstance(request);

searchContext.setAttribute(“paginationType”, “regular”);

searchContext.setEnd(searchContainer.getEnd());

//searchContext.setIncludeDiscussions(true);

searchContext.setLike(true);

searchContext.setKeywords(keyword);

searchContext.setStart(searchContainer.getStart());

Hits hits = indexer.search(searchContext);

searchContainer.setTotal(hits.getLength());

PortletURL hitURL = renderResponse.createRenderURL();

hitURL.setParameter(“struts_action”, “/test/addView”);

%>

 

<liferay-ui:search-container-results results=”<%= SearchResultUtil.getSearchResults(hits, LocaleUtil.getDefault(), portletURL)%>”/>

<liferay-ui:search-container-row

className=”com.liferay.portal.kernel.search.SearchResult”

modelVar=”searchResult”

>

<%

SampleEntry entry  =SampleEntryLocalServiceUtil.getSampleEntry(searchResult.getClassPK());

 

entry = entry.toEscapedModel();

Summary summary = searchResult.getSummary();

%>

<portlet:renderURL var=”rowURL”>

<portlet:param name=”struts_action” value=”/test/view” />

<%– <portlet:param name=”redirect” value=”<%= redirect %>” /> –%>

<portlet:param name=”urlTitle” value=”This is the Url title” />

</portlet:renderURL>

<liferay-ui:app-view-search-entry

cssClass='<%= MathUtil.isEven(index) ? “search” : “search alt” %>’

description=”<%= (summary != null) ? HtmlUtil.escape(summary.getContent()) : entry.getContent() %>”

mbMessages=”<%= searchResult.getMBMessages() %>”

queryTerms=”<%= hits.getQueryTerms() %>”

title=”<%= (summary != null) ? HtmlUtil.escape(summary.getTitle()) : entry.getTitle() %>”

url=”<%= rowURL %>”

/>

 </liferay-ui:search-container-row>

<liferay-ui:search-paginator searchContainer=”<%= searchContainer %>” type=”more” />

</liferay-ui:search-container>

 </aui:form>

 

Lets look through this code:-

 

<%

Indexer indexer = IndexerRegistryUtil.getIndexer(SampleEntry.class);

SearchContext searchContext = SearchContextFactory.getInstance(request);

searchContext.setAttribute(“paginationType”, “regular”);

searchContext.setEnd(searchContainer.getEnd());

//searchContext.setIncludeDiscussions(true);

searchContext.setLike(true);

searchContext.setKeywords(keyword);

searchContext.setStart(searchContainer.getStart());

Hits hits = indexer.search(searchContext);

searchContainer.setTotal(hits.getLength());

PortletURL hitURL = renderResponse.createRenderURL();

hitURL.setParameter(“struts_action”, “/test/addView”);

%>

First, we are getting our Indexer object. After that we are setting some attribute in searchcontext object which will be used at the time of making lucene query,  which will be implicitly handle by liferay BaseIndexer class. Over here we are putting our value in keyword so liferay will lookout in all field of document and will give the result.

This is the thing which we have to do for implementing Lucene Search in out Custom portlet.

If any one having any issue please let me know. I am hereby also attaching my portlet.

Thank You.

 Test1-portlet

 

 

Share!Share on FacebookShare on Google+Share on LinkedInTweet about this on TwitterFlattr the authorDigg thisPin on PinterestEmail this to someoneShare on StumbleUponShare on RedditShare on TumblrBuffer this pagePrint this page
Tags:
Copyright © 2017 - Scalsys. All Rights Reserved