Tuesday, 22 May 2012

Liferay + Spring MVC Portlets + Free Text Search

Liferay is one of the portal frameworks based on java. You can create portlets in Liferay using Spring MVC framework. The main purpose of this post is to provide the complete solution for free text search integration with Liferay Spring MVC portlets. If I say in short, this post may be the answer of below questions:
  • How to create a portlet in Liferay using Spring MVC
  • How to use Liferay free text search functionality in custom portlet
Liferay uses Apache Lucene to provide the free text search functionality. Apache Lucene is a high-performance, full-featured text search engine library written entirely in Java.
Example Portlets

I have created below Spring MVC portlets in Liferay to understand that how we can use free text search functionality in our web site.
1.       Add Search Content Portlet: This portlet adds the content which needs to be searched. For demo purpose, I have used three fields to add the search contents: ID, Title and Description. If you see in below screen shots, I have added three hotel names as search content in terms of ID (hotel id), Title (hotel name) and Description (about hotel).
2.       Search Portlet: This portlet provides search field from where you can search the content
which have been added using above portlet ‘Add Search Content Portlet’.  For example if you want
to search keyword like ‘Kapila Resort’.  It will search this text in all added contents and will return 
the result with matching score (Ratings).

Source Code for Example Portlets

This section provides source code for example portlets. To create the spring MVC portlet in Liferay you need to consider below points:
      Adding configuration in portlet.xml, liferay-display.xml and liferay-portlet.xml
      View Render Servlet configuration in web.xml
      Spring context configuration for View Resolver and Controller
      Creating Value Objects
      Creating Controller
      Creating JSP

For more detail about creating Spring MVC portlets in Liferay please refer.
      To create ‘Add Search Content Portlet’, follow below steps:

1.       Configure ‘applicationContext.xml’ file:

<?xml version='1.0' encoding='UTF-8'?>
<beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:context="http://www.springframework.org/schema/context"
            xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
     <import resource="classpath:spring/applicationContext-core.xml" />
      <context:annotation-config />
      <context:component-scan base-package="your.package.mvc" />
            <!-- Message source for this context, loaded from localized "messages_xx" files -->
            <bean id="messageSource"
                       class="org.springframework.context.support.ResourceBundleMessageSource">
                        <property name="basenames">
                                    <list>
                                                <value>messages</value>
                                    </list>
                        </property>
            </bean>
            <bean id="viewResolver"
                        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                        <property name="viewClass"
                                    value="org.springframework.web.servlet.view.JstlView" />
                        <property name="prefix" value="/WEB-INF/jsp/" />
                        <property name="suffix" value=".jsp" />
            </bean>
         <bean id="parameterMappingInterceptor"      
class="org.springframework.web.portlet.handler.ParameterMappingInterceptor"/>
            <bean id="portletMultipartResolver" class="org.springframework.web.portlet.multipart.CommonsPortletMultipartResolver"/>
</beans>

2.       Add below configuration in ‘portlet.xml’ file:

<portlet>
            <portlet-name>addSearchContent</portlet-name>
            <portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class>
            <init-param>
                        <name>contextConfigLocation</name>
                        <value>/WEB-INF/context/addSearchContent.xml</value>
            </init-param>
            <supports>
                        <mime-type>text/html</mime-type>
                        <portlet-mode>view</portlet-mode>
            </supports>
            <resource-bundle>content.messages</resource-bundle>
            <portlet-info>
                        <title>Add Search Contents Portlet</title>
            </portlet-info>
</portlet>

3.       Add below configuration in ‘liferay-portlet.xml’ file:

<portlet>
            <portlet-name>addSearchContent</portlet-name>
            <instanceable>true</instanceable>
</portlet>    
             

4.       Add below configuration in ‘liferay-display.xml’ file:

<category name="SearchPOC">
          <portlet id="addSearchContent"/>
</category>            

5.       Create ‘addSearchContent.xml’ file with below contents:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
            xmlns:context="http://www.springframework.org/schema/context"
            xmlns:util="http://www.springframework.org/schema/util"
            xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-3.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
                                                          
<bean class=
"org.springframework.web.portlet.mvc.annotation
.AnnotationMethodHandlerAdapter"/>
   
<bean id="portletModeHandlerMapping" class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">
                        <property name="portletModeMap">
                                    <map>
                                                <entry key="view">
                                                <ref bean="addSearchContentController" />
                                    </entry>
                                    </map>
                        </property>
    </bean>
</beans>

6.       Create value objects:

      SearchContentVO Object

package your.package.mvc.vo;
import java.util.List;
/**
 * The Class SearchContentVO.
 *
 * @author nverma
 */
public class SearchContentVO {
    /** The id. */
    private Long id;
    /** The title. */
    private String title;
    /** The description. */
    private String description;
    /** The search contents. */
    private List<SearchResult> searchContents;
    /**
     * Gets the id.
     *
     * @return the id
     */
    public Long getId() {
        return id;
    }
    /**
     * Sets the id.
     *
     * @param id the new id
     */
    public void setId(Long id) {
        this.id = id;
    }
    /**
     * Gets the title.
     *
     * @return the title
     */
    public String getTitle() {
        return title;
    }
    /**
     * Sets the title.
     *
     * @param title the title to set
     */
    public void setTitle(String title) {
        this.title = title;
    }
    /**
     * Gets the description.
     *
     * @return the description
     */
    public String getDescription() {
        return description;
    }
    /**
     * Sets the description.
     *
     * @param description the description to set
     */
    public void setDescription(String description) {
        this.description = description;
    }
    /**
     * Gets the search contents.
     *
     * @return the search contents
     */                                                                                                          
    public List<SearchResult> getSearchContents() {
        return searchContents;
    }
    /**
     * Sets the search contents.
     *
     * @param searchContents the new search contents
     */
    public void setSearchContents(List<SearchResult> searchContents) {
        this.searchContents = searchContents;
    }
}

      ‘SearchResult’ Object

package your.package.mvc.vo;
/**
 * The Class SearchResult.
 * @author nverma
 */
public class SearchResult {
    /** The id. */
    private Long id;
    /** The title. */
    private String title;
    /** The description. */
    private String description;
    /** The search score. */
    private float searchScore;
    /**
     * Gets the id.
     * @return the id
     */
    public Long getId() {
        return id;
    }
    /**
     * Sets the id.
     * @param id the new id
     */
    public void setId(Long id) {
        this.id = id;
    }
    /**
     * Gets the title.
     * @return the title
     */
    public String getTitle() {
        return title;
    }
    /**
     * Sets the title.
     * @param title the title to set
     */
    public void setTitle(String title) {
        this.title = title;
    }
    /**
     * Gets the description.
     * @return the description
     */
    public String getDescription() {
        return description;
    }
    /**
     * Sets the description.
     * @param description the description to set
     */
    public void setDescription(String description) {
        this.description = description;
    }
    /**
     * Gets the search score.
     * @return the searchScore
     */
    public float getSearchScore() {
        return searchScore;
    }
    /**
     * Sets the search score.
     * @param searchScore the searchScore to set
     */
    public void setSearchScore(float searchScore) {
        this.searchScore = searchScore;
    }
} 

7.       Create controller ‘AddSearchContentController’:

package your.package.mvc.controller;
import java.util.ArrayList;
import java.util.List;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletRequest;
import javax.portlet.RenderResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.portlet.bind.annotation.ActionMapping;
import org.springframework.web.portlet.bind.annotation.RenderMapping;
import your.package.mvc.vo.SearchContentVO;
import your.package.mvc.vo.SearchResult;
import com.liferay.portal.kernel.search.BooleanQuery;
import com.liferay.portal.kernel.search.BooleanQueryFactoryUtil;
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.Hits;
import com.liferay.portal.kernel.search.ParseException;
import com.liferay.portal.kernel.search.SearchEngineUtil;
import com.liferay.portal.kernel.search.SearchException;
/**
 * Controller to add search contents.
 * @author nverma
 */
@Controller("addSearchContentController")
@RequestMapping(value = "VIEW")
public class AddSearchContentController extends AbstractBaseController {
    /** The Constant LOG. */
    private static final Log LOG = LogFactory
            .getLog(AddSearchContentController.class);
    /** The Constant MODEL_ATT. */
    private static final String MODEL_ATT = "searchContentVO";
    /**
     * Inits the command object.
     *
     * @param portletRequest the portlet request
     * @return the search content vo
     */
    @ModelAttribute(MODEL_ATT)
    public SearchContentVO initCommandObject(final PortletRequest portletRequest) {
        LOG.debug("Initializing command object 'SearchContentVO'...");
        SearchContentVO searchContentVO = new SearchContentVO();
       searchContentVO.setSearchContents(allItems());
        return searchContentVO;
    }
    /**
     * Render add search content.
     *
     * @param renderResponse the render response
     * @return the string
     */
    @RenderMapping
    public String renderAddSearchContent(final RenderResponse renderResponse) {
        return "addSearchContent";
    }
    /**
     * Adds the search contents.
     *
     * @param searchContentVO the search content vo
     * @param actionRequest the action request
     * @param actionResponse the action response
     */
    @ActionMapping(params = "action=addSearchContent")
    public void addSearchContents(@ModelAttribute SearchContentVO searchContentVO,
            final ActionRequest actionRequest, ActionResponse actionResponse) {

        // Adding Search Document
        Document document = new DocumentImpl();
        document.addKeyword("Id", searchContentVO.getId());
        document.addText(Field.TITLE, searchContentVO.getTitle());
        document.addText("description", searchContentVO.getDescription());
        document.addText("AllDocuments", "OnlyMyItems");
        try {
            LOG.info("Adding search content...");
            SearchEngineUtil.addDocument(10132, document);    
            LOG.info("Added search content successfully.");
        } catch (SearchException e) {

            LOG.error("Failed to add search document." +e.getMessage());
        }
        searchContentVO.setSearchContents(allItems());
     }
    /**
     * Delete search contents.
     *
     * @param searchContentVO the search content vo
     * @param actionRequest the action request
     * @param actionResponse the action response
     */
    @ActionMapping(params = "action=deleteSearchContent")
    public void deleteSearchContents(@ModelAttribute SearchContentVO searchContentVO,
            final ActionRequest actionRequest, ActionResponse actionResponse) {
        try {
            LOG.info("Deleting search content...["+searchContentVO.getId()+"]");
            SearchEngineUtil.deleteDocument(10132, String.valueOf(searchContentVO.getId()));
            LOG.info("Deleted search content successfully.");
        } catch (SearchException e) {
            LOG.error("Failed to deleted search document." +e.getMessage());
        }
        searchContentVO.setSearchContents(allItems());
    }
   /**
    * All items.
    *
    * @return the list
    */
   public  List<SearchResult> allItems(){
       List<SearchResult> searchResultLst = new ArrayList<SearchResult>();
       Document[] resultDocs = null;
       Hits hits = null;
       BooleanQuery searchQuery = BooleanQueryFactoryUtil.create();
       try {
           searchQuery.addTerm("AllDocuments", "OnlyMyItems");
           hits = SearchEngineUtil.search(10132, searchQuery, -1, -1);
           resultDocs = hits.getDocs();
       } catch (ParseException e) {
           LOG.error("Failed to parse while searching. " + e.getMessage());
           e.printStackTrace();
       } catch (SearchException e) {

           LOG.error("Failed to search. " + e.getMessage());
           e.printStackTrace();
       }
       for (int i = 0; i < resultDocs.length; i++) {
           LOG.debug("Searched Items: ");
           SearchResult searchResult = new SearchResult();
           LOG.debug("Document ->  " + hits.doc(i).get(Field.UID));
         String idStr = hits.doc(i).get("Id");
           if(idStr==null || idStr.isEmpty()){
               continue;
           }
           Long  id = Long.parseLong(idStr);
           LOG.debug("Id ->  " + id);
           searchResult.setId(id);

           String title = hits.doc(i).get(Field.TITLE);
           if(title==null || title.isEmpty()){
               continue;
           }
           LOG.debug("Title ->  " + title);
           searchResult.setTitle(title);
           String description = hits.doc(i).get("description");
           LOG.debug("Description ->  " + description);
           searchResult.setDescription(description);
           //Add search result into list
           searchResultLst.add(searchResult);
       }
       return searchResultLst;
   }
}

The API marked under arrow lines is responsible to fetch the search content from JSP and then add them into search content repository.

8.       Create JSP with name ‘addSearchContent.jsp’:

<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet_2_0"%>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page contentType="text/html" isELIgnored="false"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

<portlet:actionURL var="addSearchContent">
    <portlet:param name="action" value="addSearchContent" />
</portlet:actionURL>
<c:if test="${searchContentVO != null}">
         
<table>
            <tr>
                        <td>
                            <h1>Add Search Contents in Search Repository</h1>
                        </td>
            </tr>
            <tr>
              <td>
            <form:form id="search" commandName="searchContentVO" action="${addSearchContent}">
                        <table border=0>
            <tr>
                        <td width=80 align = "center"><b>ID</b></td>
                        <td width=80 align = "center"><b>Title</b></td>
                        <td width=80 align = "center"><b>Description</b></td>                                             </tr>
            <tr>
            <td width=80 align = "center"><form:input path="id" class="textfield disabledfield"/></td>
            <td width=80 align = "center"><form:input path="title" class="textfield disabledfield"/></td>
            <td width=80 align = "center"><form:input path="description" class="textfield disabledfield"/></td>                
            </tr>                                                                                        
            </table>
                        <br/>
                        <input type="submit" value="Add Search Contents"/>                        
            </form:form>
            </td>
                        <td width=150>
                        </td>
                        <td>
                        <form:form id="search2" commandName="searchContentVO" action="${addSearchContent}">
                                    <table border=0>
                                                <tr>
                                                            <td width=80 align = "center"><b>ID</b></td>
                                                </tr>
                                               
                                                <tr>
                        <td width=80 align = "center"><form:input path="id" class="textfield disabledfield"/></td>
                                    </tr>                            
                                                         
                        </table>
                                    <br/>
                                    <input type="submit" value="Delete Search Content"/>                     
                                    </form:form>
                                    </td>
                        </table>
                        <br/>
                        <br/>
                        <h1>Added Search Contents in Search Repository</h1>
                       
                        <table border=1>
                                                <tr height=50>
                                                            <td width=80 align = "center"><b>ID</b></td>
                                                            <td width=250 align = "center"><b>Title</b></td>
                                                            <td width=480 align = "center"><b>Description</b></td>                                          
                                                </tr>
                                    <c:forEach var="searchContent" items="${searchContentVO.searchContents}">
                       
                                                <tr height=50>
                                                <td width=80 align = "center">${searchContent.id}</td>
                                                <td width=250 align = "center">${searchContent.title}</td>
                                                <td width=480>${searchContent.description}</td>                    
                                                </tr>
                
                                    </c:forEach>
                        </table>                                                 
</c:if> 
<br/>           

      To create ‘Search Portlet’, follow below steps:
1.       Add below configuration in ‘portlet.xml’ file:

<portlet>
                        <portlet-name>search</portlet-name>
                        <portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class>
                        <init-param>
                                    <name>contextConfigLocation</name>
                                    <value>/WEB-INF/context/search.xml</value>
                        </init-param>
                        <supports>
                                    <mime-type>text/html</mime-type>
                                    <portlet-mode>view</portlet-mode>
                        </supports>
                        <resource-bundle>content.messages</resource-bundle>
                        <portlet-info>
                                    <title>Search Portlet</title>
                        </portlet-info>
</portlet>

2.       Add below configuration in ‘liferay-portlet.xml’ file:

<portlet>
            <portlet-name>search</portlet-name>
            <instanceable>true</instanceable>
</portlet>
             

3.       Add below configuration in ‘liferay-display.xml’ file

<category name="SearchPOC">
          <portlet id="search"/>
</category>            

4.       Create ‘search.xml’ file with below contents

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
            xmlns:context="http://www.springframework.org/schema/context"
            xmlns:util="http://www.springframework.org/schema/util"
            xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-3.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
                                                          
<bean class="org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
   
<bean id="portletModeHandlerMapping" class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">
                        <property name="portletModeMap">
                                    <map>
                                                <entry key="view">
                                                <ref bean="searchController" />
                                    </entry>
                                    </map>
                        </property>
    </bean>
</beans>

5.       Create Value object ‘SearchVO’:

package your.package.mvc.vo;
import java.util.List;
/**
 * The Class SearchVO.
 *
 * @author nverma
 */
public class SearchVO {
    /** The no search result flag. */
    private boolean noSearchResultFlag;
    /** The search words. */
    private String searchWords;

    /** The hotel search result. */
    private List<SearchResult> searchResult;
    /**
     * Checks if is no search result flag.
     *
     * @return true, if is no search result flag
     */
    public boolean isNoSearchResultFlag() {
        return noSearchResultFlag;
    }
    /**
     * Sets the no search result flag.
     *
     * @param noSearchResultFlag the new no search result flag
     */
    public void setNoSearchResultFlag(boolean noSearchResultFlag) {
        this.noSearchResultFlag = noSearchResultFlag;
    }
    /**
     * Gets the search words.
     *
     * @return the search words
     */
    public String getSearchWords() {
        return searchWords;
    }
    /**
     * Sets the search words.
     *
     * @param searchWords the new search words
     */
    public void setSearchWords(String searchWords) {
        this.searchWords = searchWords;
    }

    /**
     * Gets the search result.
     *
     * @return the search result
     */
    public List<SearchResult> getSearchResult() {
        return searchResult;
    }

    /**
     * Sets the search result.
     *
     * @param searchResult the new search result
     */
    public void setSearchResult(List<SearchResult> searchResult) {
        this.searchResult = searchResult;
    }
}

6.       Create controller  ‘SearchController’:

package your.package.controller;
import java.util.ArrayList;
import java.util.List;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletRequest;
import javax.portlet.RenderResponse;
import com.liferay.portal.kernel.search.BooleanQuery;
import com.liferay.portal.kernel.search.BooleanQueryFactoryUtil;
import com.liferay.portal.kernel.search.Document;
import com.liferay.portal.kernel.search.Field;
import com.liferay.portal.kernel.search.Hits;
import com.liferay.portal.kernel.search.ParseException;
import com.liferay.portal.kernel.search.SearchEngineUtil;
import com.liferay.portal.kernel.search.SearchException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.portlet.bind.annotation.ActionMapping;
import org.springframework.web.portlet.bind.annotation.RenderMapping;
import your.package.mvc.vo.SearchResult;
import your.package.mvc.vo.SearchVO;
/**
 * Controller for free text search.
 *
 * @author nverma
 */
@Controller("searchController")
@RequestMapping(value = "VIEW")
public class SearchController extends AbstractBaseController {
    /** The Constant LOG. */
    private static final Log LOG = LogFactory
            .getLog(SearchController.class);
    /** The Constant MODEL_ATT. */
    private static final String MODEL_ATT = "searchVO";
    /**
     * Inits the command object.
     *
     * @param portletRequest the portlet request
     * @return the search vo
     */
    @ModelAttribute(MODEL_ATT)
    public SearchVO initCommandObject(final PortletRequest portletRequest) {
        LOG.debug("Initializing command object 'SearchVO'...");
       return new SearchVO();
    }
    /**
     * Render search.
     *
     * @param renderResponse the render response
     * @return the string
     */
    @RenderMapping
    public String renderSearch(final RenderResponse renderResponse) {
        return "search";
    }
    /**
     * Render search result.
     *
     * @param renderResponse the render response
     * @return the string
     */
    @RenderMapping(params = "action=showSearchResult")
    public String renderSearchResult(final RenderResponse renderResponse) {
        return "search";
    }

    /**
     * Search.
     *
     * @param searchVO the search vo
     * @param actionRequest the action request
     * @param actionResponse the action response
     */
    @ActionMapping(params = "action=search")
    public void search(@ModelAttribute SearchVO searchVO,
            final ActionRequest actionRequest, ActionResponse actionResponse) {
        List<SearchResult> searchResultLst = new ArrayList<SearchResult>();
        String searchKeyWords = searchVO.getSearchWords();

        LOG.debug("Searing for input keywords  [" + searchKeyWords + "]...");
        Document[] resultDocs = null;
        Hits hits = null;
        BooleanQuery searchQuery = BooleanQueryFactoryUtil.create();

        try {
            searchQuery.addTerm(Field.TITLE, searchKeyWords);
            searchQuery.addTerm("description", searchKeyWords);
            hits = SearchEngineUtil.search(10132, searchQuery, -1, -1);
            resultDocs = hits.getDocs();
        } catch (ParseException e) {
            LOG.error("Failed to parse while searching. " + e.getMessage());
            e.printStackTrace();
        } catch (SearchException e) {
            LOG.error("Failed to search. " + e.getMessage());
            e.printStackTrace();
        }
        if(hits == null || hits.getLength() == 0){
            searchVO.setNoSearchResultFlag(true);
        }
        LOG.debug(" Search result count [" + hits.getLength() + "]");
       for (int i = 0; i < resultDocs.length; i++) {
            LOG.debug("Searched Items: ");
            SearchResult searchResult = new SearchResult();
            LOG.debug("Document ->  " + hits.doc(i).get(Field.UID));
           String onlyForHotel = hits.doc(i).get("AllDocuments");
            if(!"OnlyMyItems".equals(onlyForHotel)){
                continue;
            }
       String idStr = hits.doc(i).get("Id");
            if(idStr==null || idStr.isEmpty()){
                continue;
            }
            Long  id = Long.parseLong(idStr);
            LOG.debug("Id ->  " + id);
            searchResult.setId(id);
            String title = hits.doc(i).get(Field.TITLE);
            LOG.debug("Title ->  " + title);
            searchResult.setTitle(title);

            String description = hits.doc(i).get("description");
            LOG.debug("Description ->  " + description);
            searchResult.setDescription(description);
            float score = hits.score(i);
            LOG.debug("Result Score ->  " + hits.score(i));
            searchResult.setSearchScore(score);
            //Add search result into list
            searchResultLst.add(searchResult);
        }

        searchVO.setSearchResult(searchResultLst);
       actionResponse.setRenderParameter("action", "showSearchResult");

        LOG.debug("Searched successfully.");
    }
}

The API marked under arrow lines is responsible to perform the search for given keywords and set the result into VO to render on JSP.

7.       Create JSP ‘search.jsp’:

<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet_2_0"%>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page contentType="text/html" isELIgnored="false"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<portlet:actionURL var="searchUrl">
    <portlet:param name="action" value="search" />
</portlet:actionURL>
<c:if test="${searchVO != null}">
   <form:form id="search" commandName="searchVO" action="${searchUrl}">
            <form:input path="searchWords"  value = "Search Words" class="textfield disabledfield"/>
       <input type="submit" value="Search"/>
   </form:form>     
</c:if> 
<br/><br/>
<h3>Search Result</h3>
<form:form id="searchItem" commandName="searchVO">
<table border=1>
                        <tr height=50>
                                    <td width=80 align = "center"><b>Search ID</b></td>
                                    <td width=250 align = "center"><b>Title</b></td>
                                    <td width=480 align = "center"><b>Description</b></td>
                                    <td width=140 align = "center"><b>Ratings</b></td>
                        </tr>
            <c:forEach var="searchResult" items="${searchVO.searchResult}">
            <tr height=50>
                                    <td width=80 align = "center"><a href="/search?searchId=${searchResult.id}">${searchResult.id}</a></td>
                                    <td width=250 align = "center"><a href="/search?searchId=${searchResult.id}">${searchResult.title}</a></td>
                                    <td width=480 align = "center"><a href="/search?searchId=${searchResult.id}">${searchResult.description}</a></td>
                               <td width=140 align = "center"><liferay-ui:ratings-score score="${searchResult.searchScore*5}"/></td>
                        </tr>
           
            </c:forEach>
</table>                                   
</form:form>

After configuring all above source files you can build your application and then deploy on application server. Access your Liferay server, add a test page and drop both portlets on test page and test the search functionality by adding search content first and then searching keywords.

                                   :) Happy Free Text Searching :)

See More: