Child pages
  • Elasticsearch Integration
Skip to end of metadata
Go to start of metadata

Introduction

Summary

This app provides integration with Elasticsearch, a flexible and powerful open source, distributed, real-time search and analytics engine. Its major features includes content indexing and on top of Liferay default search we provide autosuggestions. With this integration, your Liferay Portal will use Elasticsearch for indexing and searching.

Elasticsearch (v1.2.1)

Elasticsearch is a flexible and powerful open source, distributed, real-time search and analytics engine. Elasticsearch provides full text search and is architected ground up for distributed environment where scalability and reliability are must haves. It provides robust APIs, query DSLs and also clients in most popular programming languages. All communication between Elasticsearch server and the client uses REST API which can be executed over browsers or command lines.

Elasticsearch Web Plugin

Elasticsearch Web Plugin is a web plugin designed for Liferay. It acts as an adapter the same way Liferay's Solr-web plugin acts and enables switching to Elasticsearch a hot-deployable one.

Elasticsearch Portlet

Elasticsearch Portlet is a whitelisted Liferay plugin which is built to provide autosuggestions in Liferay's search portlet. Suggestions are displayed as user types in the search box and can find information quickly by seeing result predictions that contains search terms typed by end user. Facet and scope group selections are considered while retrieving suggestions.For example, as you start to type test, you may see suggestions such as Test Test user (default user in Liferay) and any other content that has text test.

Elasticsearch common hook

A common hook with properties required for both Elasticsearch plugins. This hook will have the details of Elasticsearch nodes, installation location and any other portal level details.

Flow

Indexing Flow

Search Flow

Concepts

Key concepts from elasticsearch (http://www.elasticsearch.org/resources/)

Client (http://www.elasticsearch.org/guide/en/elasticsearch/client/java-api/current/client.html)

An object that allows us to access all the APIs that elasticsearch offers. Clients can be of the following types - Node client or transport client. Please refer to the Elasticsearch documentation for more information on the differences between them and how to configure them.

Cluster (http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/modules-cluster.html)

A cluster is a group of nodes that are detected automatically within the same network. For the purpose of this plugin and documentation we will use the default cluster and default multicast for node detection.

Node (http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/modules-node.html)

A node is a server that holds data and indexes. A non-data node can be created when a client is created and it tries to access a cluster without any actual data in the local system. A node (server) can be shutdown to stop the service. A non-data node also needs to be shutdown (closed) manually when it is not in use.

Indices (http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/modules-indices.html)

Indices are stored in elasticsearch nodes and are distributed based on number of shards configured (or default) and replication.

JSON

Elasticsearch uses RESTful API for all communication with clients. All queries are made in JSON string format and delivered to and from the elasticsearch server.

A free tool to interact with this API and check on the indexes status is: https://github.com/mobz/elasticsearch-head (it may need a CORS pluging for the browser)

Configuration

Elasticsearch Server Configuration (http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/setup.html)

Elasticsearch server can be run as a server or service. Elasticsearch is built using Java and requires at least Java 7 in order to run.

Start elasticsearch server

// Once elasticsearch bundle is downloaded
$cd {downloaded elasticsearch server location}
$cd bin
$./elasticsearch

Once the server started, you can check the list of nodes in the cluster (default is "elasticsearch")

or status of nodes as following

Update the hook

Update the portal.properties to match the Elasticsearch server configuration and deploy it. 

  • elasticsearch.node: A comma separated list of ip addresses and their port numbers of the nodes that runs in the same cluster. elasticsearch will replicate the indexes to the nodes specified in the list which doesn't require broadcasting of the update. This is available in "Transport Client". "Node Client" uses broadcasting of the update notification which doesn't require the configuration with the node list, but it is heavier than transport client due to the broadcasting mechanism. Also, we found some issues in index replication processes where the replication was not happening properly between nodes.
portal.properties
# Name of the cluster. This should match the name of the cluster in 
# {elastic search server location}/elasticsearch-1.2.1/config/elasticsearch.yml file
# Note that the cluster name have to be set if different than "elasticsearch"
# ex: elasticsearch.clusterName=myelasticsearch

# Address of at least one Elasticsearch node in the cluster. 
# The plugin will get from that node the rest of the nodes in the cluster, and use all of those when round robin requests. 
 elasticsearch.node={comma separated node list with port numbers}
# ex: elasticsearch.node=192.168.1.4:9300

# Location of the elasticsearch server in the file system
 elasticsearch.homeFile={path to elasticsearch server}/elasticsearch-1.2.1
# ex: elasticsearch.homeFile=/folder/subfolder/server/elasticsearch-1.2.1

Elasticsearch Web Plugin Configuration

The portlet level properties file will consist the needed configuration for Indexing and Search related actions. The indexExcludedType field will exclude those corresponding documents from Indexing to Elasticsearch server.

portlet.properties
#
# Elasticsearch Web Plugin properties
#

#These entry classnames will be excluded from indexing
indexExcludedType=com.liferay.portal.kernel.plugin.PluginPackage,com.liferay.portal.kernel.lar.ExportImportHelper
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	id="WebApp_ID" version="2.5">
	<context-param>
		<param-name>portalContextConfigLocation</param-name>
		<param-value>/WEB-INF/classes/META-INF/elasticsearch-spring.xml</param-value>
	</context-param>
	<listener>
		<listener-class>com.liferay.portal.kernel.spring.context.PortletContextLoaderListener</listener-class>
	</listener>
</web-app>
elasticsearch-spring.xml
<?xml version="1.0"?>
<beans default-destroy-method="destroy" default-init-method="afterPropertiesSet" xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">
    <bean class="com.liferay.portal.spring.context.PortletBeanFactoryPostProcessor" />
    <!-- Search -->
    <bean id="queryBuilder" class="com.rivetlogic.portal.search.elasticsearch.querybuilder.ElasticsearchQueryBuilder" />
    <bean id="searchHelper" class="com.rivetlogic.portal.search.elasticsearch.util.ElasticsearchHelper">
        <property name="esConnector" ref="elasticsearchConnector" />    
        <property name="esQueryBuilder" ref="queryBuilder" />
    </bean>
    <bean id="indexSearcher.elasticsearch" class="com.rivetlogic.portal.search.elasticsearch.ElasticsearchIndexSearcherImpl">
        <property name="esSearchHelper" ref="searchHelper" />
    </bean>
    <!-- Indexer -->
    <bean id="elasticsearchDocumentJSONBuilder" class="com.rivetlogic.portal.search.elasticsearch.indexer.document.ElasticsearchDocumentJSONBuilder" init-method="loadExcludedTypes" />
    <bean id="elasticsearchConnector" class="com.rivetlogic.portal.search.elasticsearch.util.ElasticsearchConnector" init-method="initESSetup" destroy-method="close" />
    <bean id="elasticsearchIndexer" class="com.rivetlogic.portal.search.elasticsearch.indexer.ElasticsearchIndexerImpl">
        <property name="documentJSONBuilder" ref="elasticsearchDocumentJSONBuilder" />
    </bean>
    <bean id="indexWriter.elasticsearch" class="com.rivetlogic.portal.search.elasticsearch.ElasticsearchIndexWriterImpl">
        <property name="indexer" ref="elasticsearchIndexer" />
        <property name="esConnector" ref="elasticsearchConnector" />
    </bean>
    <!-- Search Engine -->
    <bean id="searchEngine.elasticsearch" class="com.liferay.portal.kernel.search.BaseSearchEngine">
        <property name="clusteredWrite" value="false" />
        <property name="indexSearcher" ref="indexSearcher.elasticsearch" />
        <property name="indexWriter" ref="indexWriter.elasticsearch" />
        <property name="luceneBased" value="true" />
        <property name="vendor" value="ELASTICSEARCH" />
    </bean>
    <!-- Configurator -->
    <bean id="searchEngineConfigurator.elasticsearch" class="com.liferay.portal.kernel.search.PluginSearchEngineConfigurator">
		<property name="searchEngines">
			<util:map>
				<entry key="SYSTEM_ENGINE" value-ref="searchEngine.elasticsearch" />
			</util:map>
		</property>        
    </bean>
    <!-- ===== MESSAGING ===== -->
    <!-- Message source -->
    <bean id="messageSender.elasticsearch_plugin" class="com.rivetlogic.portal.messaging.sender.PluginMessageSender" />
    <bean id="messageListener.bridging.elasticsearch" class="com.liferay.portal.kernel.messaging.BridgingMessageListener">
        <property name="singleDestinationMessageSender">
            <bean class="com.liferay.portal.kernel.messaging.sender.DefaultSingleDestinationMessageSender">
                <property name="destinationName" value="liferay/search_writer/ELASTICSEARCH" />
                <property name="messageSender" ref="messageSender.elasticsearch_plugin" />
            </bean>
        </property>
    </bean>
    <!-- Listeners -->
    <bean id="messageListener.elasticsearch_reader" class="com.liferay.portal.kernel.search.messaging.SearchReaderMessageListener">
        <property name="searchEngine" ref="searchEngine.elasticsearch" />
    </bean>
    <bean id="messageListener.elasticsearch_writer" class="com.liferay.portal.kernel.search.messaging.SearchWriterMessageListener">
        <property name="searchEngine" ref="searchEngine.elasticsearch" />
    </bean>
    <!-- Destinations -->
    <bean id="destination.elasticsearch_searcher" class="com.liferay.portal.kernel.messaging.ParallelDestination">
        <constructor-arg index="0" type="java.lang.String" value="liferay/search_reader/ELASTICSEARCH" />
    </bean>
    <bean id="destination.elasticsearch_writer" class="com.liferay.portal.kernel.messaging.ParallelDestination">
        <constructor-arg index="0" type="java.lang.String" value="liferay/search_writer/ELASTICSEARCH" />
    </bean>
    <!-- Routing -->
    <bean id="messagingConfigurator.elasticsearch" class="com.liferay.portal.kernel.messaging.config.PluginMessagingConfigurator" depends-on="searchEngine.elasticsearch">
        <property name="destinations">
            <list>
            	<ref bean="destination.elasticsearch_searcher" />
                <ref bean="destination.elasticsearch_writer" />
            </list>
        </property>
        <property name="messageListeners">
            <map key-type="java.lang.String" value-type="java.util.List">
                <entry key="liferay/search_reader/ELASTICSEARCH">
                    <list value-type="com.liferay.portal.kernel.messaging.MessageListener">
                        <ref bean="messageListener.elasticsearch_reader" />
                    </list>
                </entry>
                <entry key="liferay/search_writer/SYSTEM_ENGINE">
                    <list value-type="com.liferay.portal.kernel.messaging.MessageListener">
                        <ref bean="messageListener.bridging.elasticsearch" />
                    </list>
                </entry>
                <entry key="liferay/search_writer/ELASTICSEARCH">
                    <list value-type="com.liferay.portal.kernel.messaging.MessageListener">
                        <ref bean="messageListener.elasticsearch_writer" />
                    </list>
                </entry>
            </map>
        </property>
    </bean>
</beans>

Elasticsearch Portlet Configuration

This sections covers the required configuration for Elasticserach Portlet (autosuggestions feature) plugin. The portlet level properties file will consist the needed configuration for autosuggestions feature. Customize and redeploy the plugin to achieve desired result.

Note: Optional fields will have default values.

portlet.properties
#
# Autosuggestion related properties
#


# A comma separated list of Fields to be queried to obtain Autosuggestions for search phrase with respective boost value
# Different weights can be assigned to matches at different positions allowing for things like phrase matches 
# being sorted above term matches when highlighting a Boosting Query that boosts phrase matches over term matches
# Format: fieldName^boostValue OR fieldName
# Ex: title^10,lastName,screenName
 suggestionQueryFields=title^10,userName^8,author^9,content^3,firstName^8,lastName,screenName

# Comma separated list of Liferay objects/Elasticsearch index types to be excluded from 
# autosuggestion query
# A liferay object maps to a corresponding Elasticsearch index type, so the objects can be
# added here to be excluded
 suggestionQueryExcludedType=com.liferay.portal.model.Contact

# Optional: Number of suggestions to be displayed when a user is typing in search portlet, 
# if not specified defaults to 4
# ex: autoSuggestionsSize=6

# Optional: The suggestion query size parameter allows to configure the maximum amount of hits to be returned from its query.
# if not specified defaults to 10 which is the default query size in Elasticsearch
# ex: autoSuggestionsQueryMaxHits=15

# Optional: Length of the text to be displayed as a suggestion,
# if not specified defaults to 50
# ex:autoSuggestionLength=80
  • suggestionQueryFields: The fields to be queried to fetch autosuggestions. Certain fields can be boosted to have more weight by providing boost value followed by Caret(^).
    Boost value is not mandatory and those fields will have least preference in the results. 
  • suggestionQueryExcludedType: There could be a need to exclude few index/object(a liferay object is mapped to Elasticsearch index type) types from suggestions. 
    By default Contact is excluded from suggestion results to avoid redundant results when looking up username/screename/any other user details. 

     

  • autoSuggestionsSize: The number of unique suggestions to be displayed, by default it is set 4.
  • autoSuggestionsQueryMaxHits: The field will limit the number of records retrieved by suggestion query from ES server.This is same as size parameter which allows to configure the maximum amount of hits to be returned from its query.

     

  • autoSuggestionLength: The length of the suggestion to be shown in suggestions. This can be very handy while the suggestion had length values such as Content from DL Media section.

Implementation

Elasticsearch Web plugin

This is a plugin that intercepts Liferay's indexing messages and redirects the process to the elasticsearch server instead of Liferay's Lucene indexer.

Connector

Elasticsearch provides several options for us to create connection to easticsearch server.

NodeClient: This client uses broadcasting of index update which doesn't require any pre configuration. All nodes in the same cluster is recognized automatically and index replication happens autonomously throughout the nodes in the same cluster.

TransportClient: This client uses direct connection to the nodes in the same cluster which requires a pre-configuration with a list of nodes' ip address and the port number of each node. This client is lighter than NodeClient implementation due to the fact that it already knows where to replicate the indexes.

ElasticsearchConnector.java
public class ElasticsearchConnector {
	private String propsFile;
	private Client = null;
	private Properties prop;
 
	public Properties getProps() {
		return prop;
	}
 
	public void setPropsFile(String propsFile) {
		this.propsFile = propsFile;
	}
 
	public void initESSetup() {
		// initializer of elasticsearch connection
 
		// load node list from "elastic.props" properties file
 
		// establish transport client for each node
 
		// client object created is used by index writer/searcher
	}
 
	public void close() {
		...
		client.close();
		...
	}
 
	public Client getClient() {
		return client;
	}
 
	private boolean isLiferayIndexExists() {
		// checks if "liferay" index exists in elasticsearch server
	}
 
	private void createliferayIndexInESServer() {
		// create a mapping aliases for different field types
		// field type is used when queries are executed against elasticsearch
		// based on the type, elasticsearch tries to convert the data from response to correct type
		// which uses the alias to overcome wrong type related exceptions
	}
}

Document Transformer

ElasticsearchJSONDocument

Class contains JSON converted document object along with its id and related data including error messages

ElasticsearchJSONDocument.java
public class ElasticsearchJSONDocument {
	private String id;
	private String indexType;
	private String jsonDocument;
	private String errorMessage;
	private boolean isError;
 
	public enum DocumentError {
		HIDDEN_DOCUMENT, MISSING_CLASSPK, MISSING_ENTRYCLASSNAME, EXCLUEDED_TYPE;
		private String errorMsg;
		private DocumentError(String value) {
			this.errorMsg = value;
		}
		public String toString() {
			return errorMsg;
		}
	};
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getIndexType() {
        return indexType;
    }
    public void setIndexType(String indexType) {
        this.indexType = indexType;
    }
    public String getJsonDocument() {
        return jsonDocument;
    }
    public void setJsonDocument(String jsonDocument) {
        this.jsonDocument = jsonDocument;
    }
    public String getErrorMessage() {
        return errorMessage;
    }
    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }
    public boolean isError() {
        return isError;
    }
    public void setError(boolean isError) {
        this.isError = isError;
    }
} 

ElasticsearchDocumentJSONBuilder

Class that converts Document object into JSON object

ElasticsearchDocumentJSONBuilder.java
public class ElasticsearchDocumentJSONBuilder {
	private Set<String> excludedTypes;
	private ElasticsearchConnector _esConnector;
 
	public void setEsConnector(ElasticsearchConnector esConnector) {
		this._esConnector = esConnector;
	}
 
	public void loadExcludedTypes() {
		// load excluded types from elastic.props properties file
	}
 
	public ElasticesearchJSONDocument convertToJSON(final Document document) {
		// converts the elements of document object into JSON form
 
		// filters out some special cases like hidden folders, empty entryClassName case and excluded type of entryClassName cases
	}
 
	private boolean isDocumentHidden(Document document) {
		// checks if the document object is hidden object in Liferay
	}
 
	private boolean isExcludedType(String indexType) {
		// checks if the document object is of excluded class types
	}
}

 

Indexer

Class that triggers actual document conversion processes from the Document Trasnformer

ElasticsearchIndexerImpl.java
public class ElasticsearchIndexerImpl implements ElasticsearchIndexer {
	private ElasticsearchDocumentJSONBuilder documentJSONBuilder;
	
	public void setDocumentJSONBuilder(ElasticsearchDocumentJSONBuilder documentJSONBuilder) {
		return documentJSONBuilder;
	}
	public Collection<ElasticserachJSONDocument> processDocuments(Collection<Document> documents) throws ElasticsearchIndexingException {
		// process multiple documents as bulk
    }

    public ElasticserachJSONDocument processDocument(Document document) throws ElasticsearchIndexingException {
		// process a single document object
    }
} 

Index Writer

Class that add, update or delete indexes on elasticsearch

ElasticsearchIndexWriterImpl.java
public class ElasticsearchIndexerWriterImpl extends BaseIndexWriter {
	private ElasticsearchIndexer _indexer;
	private ElasticsearchConnector _esConnector;
 
	public void setEsConnector(ElasticsearchConnector esConnector) {
        this._esConnector = esConnector;
    }
 
    public void setIndexer(ElasticsearchIndexer indexer) {
        this._indexer = indexer;
    }
 
	public void addDocument(SearchContext searchContext, Document document) throws SearchException {
		// add a new document to elasticsearch index
	}
 
	public void deleteDocument(SearchContext searchContext, String uid) throws SearchException {
		// invoke delete index process using the uid
	}
 
	public void updateDocument(SearchContext searchContext, Document document) throws SearchException {
		// invoke update index process
		// update will overwrite current existing index using the same UID passed in from the document object
	}
} 

Index Searcher

ElasticsearchIndexSearcherImpl

Class that executes the actual search against elasticsearch via ElasticsearchHelper

ElasticsearchIndexSearcherImpl.java
public class ElasticsearchIndexSearcherImpl extends BaseIndexSearcher {
	private ElasticsearchHelper _esSearchHelper;
 
	public ElasticsearchHelper getEsSearchHelper() {
		return _esSearchHelper;
	}
	public void setEsSearchHelper(ElasticsearchHelper esSearchHelper) {
		this._esSearchHelper = esSearchHelper;
	}

    @Override
    public Hits search(SearchContext searchContext, Query query) throws SearchException {
		// invoke search process from ElasticsearchHelper class
    }
} 

ElasticsearchHelper

Helper class that establishes and executes the search triggered from ElasticsearchIndexerSearcherImpl

ElasticsearchHelper.java
public class ElasticsearchHelper {
	private ElasticsearchConnector _esConnector;
	private ElasticsearchQueryBuilder _esQueryBuilder;
	
	public ElasticsearchConnector getEsConnector() {
        return _esConnector;
    }
	public void setEsConnector(ElasticsearchConnector esConnector) {
        this._esConnector = esConnector;
    }
	public ElasticsearchQueryBuilder getEsQueryBuilder() {
        return _esQueryBuilder;
    }
	public void setEsQueryBuilder(ElasticsearchQueryBuilder esQueryBuilder) {
        this._esQueryBuilder = esQueryBuilder;
    }
 
	public Hits getSearchHits(SearchContext searchContext, Query query) {
		// get client from the connector object
		// create SearchRequestBuilder which loads query built from _esQueryBuilder
		// executes the query against elasticsearch and gets SearchHits from the response
		// set up documents and other related information to hits which Liferay will use to render the search results
	}
 
	private SortBuilder getSort(SearchContext searchContext) {
		// set up SortBuilder from sort information in SearchContext
	}
	
	private Float[] getScores(SearchHits searchHits) {
		// get scores from searchHits to update Hits object for Liferay to use
	}
 
	private Document[] getDocuments(SearchHits searchHits) {
		// get results from searchHits in JSON form
		// transform JSON objects into Document objects so Liferay can use
	}
 
	private Map<String, Integer> parseESFacet(Facet esFacet) {
		// returns a map of facets and their counts
	}
 
	private void handleFacetQueries(SearchContext searchContext, SearchRequestBuilder searchRequestBuilder) {
		// compares request facets from Liferay against facets returned from elasticsearch
		// and updates the facets for the search results with the latest facet information including the updated counts
	}
 
	private void collectFacetResults(SearchContext searchContext, SearchResponse response) {
		// collect facets from the search response
		// and transform them into Liferay's facet form so Liferay can use them to render the facets properly
	}
 
	private String buildRangeTerm(Entry entry) {
		// build range query for elasticsearch use for range facet
	}
 
	private Set<String> facetEntryClassnames(Facet liferayFacet) {
		// fetch entry classnames
	}
 
	private String[] fetchFromToValuesInRange(JSONObject jsonObject) {
		// fetch from to values in range
	}
} 

Query Builder

Class that utilizes Liferay's query object because elasticsearch is based on Lucene indexer implementations

ElasticsearchQueryBuilder.java
public class ElasticsearchQueryBuilder{
	public QueryStringQueryBuilder doSearch(Query query) {
		return QueryBuilders.queryString(query.toString());
	}
} 
 

Facets

ElasticsearchDefaultTermCollector

ElasticsearchDefaultTermCollector.java
public class ElasticsearchDeaultTermCollector implements TermCollector {
	private String term;
	private int frequency;
 
	public ElasticsearchDefaultTermCollector(String term, int frequency) {
		this.term = term;
		this.frequency = frequency;
	}	
 
	public String getTerm() {
		return this.term;
	}
 
	public int getFrenquency() {
		return this.frequency;
	}
}

ElasticsearchQueryFacetCollector

Class to convert elasticsearch facets into Liferay's facets

ElasticsearchQueryFacetCollector
 public class ElasticsearchQueryFacetCollector implements FacetCollector {
	private Map<String, Integer> _counts = new HashMap<String, Integer>();
	private String _fieldName;
	private List<TermCollector> _termCollectors;
 
	public ElasticsearchQueryFacetCollector(String feildName, Map<String, Integer> facetResults) {
		// update fieldName and its count
	}
 
	public String getFieldName() {
		return this.fieldName;
	}
 
	public TermCollector getTermCollector(String term) {
		// returns a new ElasticsearchDefaultTermCollector with the term and its count
	}
 
	public List<TermCollector> getTermCollectors() {
		// return a list of termCollectors
	}
}

Elasticsearch Portlet

The Elasticsearch Portlet also has a hook within to update the behavior of Search portlet.

Elasticsearch Portlet (Hidden portlet)

This is a hidden portlet that provides infrastructure for search portlet such as dynamic search suggestions and establishing the connection to the elasticsearch server

Portlet controller class

ElasticsearchPortlet has a resource method to obtain the suggestions. The front end will use Liferay's resource URL through AJAX call to display them as user types.

ElasticsearchPortlet
public class ElasticsearchPortlet extends MVCPortlet {
    /*
     * (non-Javadoc)
     * 
     * @see javax.portlet.GenericPortlet#init(javax.portlet.PortletConfig)
     */
    @Override
    public void init(PortletConfig portletConfig) {
        super.init(portletConfig);
		.....
    }
    /*
     * (non-Javadoc)
     * 
     * @see javax.portlet.GenericPortlet#destroy()
     */
    @Override
    public void destroy() {
        super.destroy();
        .....
    }
    /*
     * (non-Javadoc)
     * 
     * @see com.liferay.util.bridges.mvc.MVCPortlet#serveResource(javax.portlet.
     * ResourceRequest, javax.portlet.ResourceResponse)
     */
    @Override
    public void serveResource(ResourceRequest resourceRequest, ResourceResponse resourceResponse) {
		//Get the suggestions using PortletHelper class
    }
}

Portlet Helper

ElasticsearchPortletHelper class has all the helper/utility methods needed for this Portlet.

ElasticsearchPortletHelper
public class ElasticsearchPortletHelper {
    /**
     * Creates a transport client to interact with Elasticsearch servers.
     */
    public void createClient() {
		//Initiates a transport client
    }
    /**
     * A method to closes client properly to avoid memory leaks.
     */
    public void destroyClient() {
    }
    /**
     * Fetch auto suggestions.
     *
     * @param resourceRequest the resource request
     * @param resourceResponse the resource response
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws ElasticsearchAutocompleteException the elasticsearch autocomplete exception
     */
    public void fetchAutoSuggestions(ResourceRequest resourceRequest, ResourceResponse resourceResponse)
            throws IOException, ElasticsearchAutocompleteException {
        PrintWriter out = resourceResponse.getWriter();
        out.println(parseResponse(response));
    }
    /**
     * Creates the Elasticsearch request builder with all necessary settings.
     */
    private void createESRequestBuilder() {
    }
    /**
     * Gets the transport hosts.
     *
     * @return the transport hosts
     */
    private InetSocketTransportAddress[] getTransportHosts() {
        return transportAddresses;
    }
    /**
     * Append query fields to builder.
     *
     * @param builder the builder
     */
    private void appendQueryFieldsToBuilder(QueryStringQueryBuilder builder) {
    }
    /**
     * Parses the response.
     *
     * @param response the response
     * @return the string
     */
    private String parseResponse(SearchResponse response) {
		//return the formatted suggestions
        return arraySuggestions.toString();
    }
}

PropsValues

ElasticsearchPropsValues class is used to load all required properties and convert them to a desired format.

ElasticsearchPropsValues
public class ElasticsearchPropsValues {
    /**
     * Gets the property.
     * 
     * @param key
     *            the key
     * @return the property
     */
    public String getProperty(String key) {
        return props.getProperty(key);
    }
    /**
     * Gets the property.
     * 
     * @param key
     *            the key
     * @param defaultValue
     *            the default value
     * @return the property
     */
    public int getProperty(String key, int defaultValue) {
        return GetterUtil.getInteger(getProperty(key), defaultValue);
    }
    /**
     * Gets the suggestions size.
     * 
     * @return the suggestions size
     */
    public int getSuggestionsSize() {
        return suggestionsSize;
    }
    /**
     * Gets the suggestions length.
     * 
     * @return the suggestions length
     */
    public int getSuggestionsLength() {
        return suggestionsLength;
    }
    /**
     * Gets the es server home.
     * 
     * @return the es server home
     */
    public String getEsServerHome() {
        return esServerHome;
    }
    /**
     * Gets the suggestion query fields.
     * 
     * @return the suggestion query fields
     */
    public Map<String, Float> getSuggestionQueryFields() {
        return suggestionQueryFields;
    }
    /**
     * Gets the suggestion excluded types.
     * 
     * @return the suggestion excluded types
     */
    public Set<String> getSuggestionExcludedTypes() {
        return suggestionExcludedTypes;
    }
    
    ............
}

Elasticsearch Hook

This is a hook plugin embedded in elasticsearch portlet. It is to provide customized behavior of Liferay's search portlet with dynamic search suggestion feature. The customization was done by updating Liferay's search tag library JSP code as well as some JSP code, Javascript and CSS updates for the search portlet.

liferay-hook.xml
<?xml version="1.0"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 6.2.0//EN" "http://www.liferay.com/dtd/liferay-hook_6_2_0.dtd">
<hook>
    <portal-properties>content/portal.properties</portal-properties>
	<custom-jsp-dir>/META-INF/custom_jsps</custom-jsp-dir>
</hook> 

Screenshots

Search SuggestionsSearch Results

 

PACL Security Manager

PACL security manager is disabled due to the nature of the plugin's functionality where it needs to access the external sources to establish the connection to elasticsearch server which resides outside the application container. 

Plugin Source Code

The code being developed is maintained in the following SVN location (SDK/webs/elasticsearch-web).

https://svn.rivetlogic.com/repos/rivets/archives/hrportal/branches/6.2%20CE%20GA%201/liferay-plugins-sdk-6.2.0

  • No labels