All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.liferay.portal.search.elasticsearch6.internal.ElasticsearchQuerySuggester Maven / Gradle / Ivy

/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */

package com.liferay.portal.search.elasticsearch6.internal;

import com.liferay.petra.string.StringPool;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.search.Field;
import com.liferay.portal.kernel.search.SearchContext;
import com.liferay.portal.kernel.search.SearchException;
import com.liferay.portal.kernel.search.suggest.AggregateSuggester;
import com.liferay.portal.kernel.search.suggest.PhraseSuggester;
import com.liferay.portal.kernel.search.suggest.QuerySuggester;
import com.liferay.portal.kernel.search.suggest.Suggester;
import com.liferay.portal.kernel.search.suggest.SuggesterResult;
import com.liferay.portal.kernel.search.suggest.SuggesterResults;
import com.liferay.portal.kernel.search.suggest.SuggesterTranslator;
import com.liferay.portal.kernel.search.suggest.TermSuggester;
import com.liferay.portal.kernel.util.Localization;
import com.liferay.portal.kernel.util.LocalizationUtil;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.search.elasticsearch6.internal.connection.ElasticsearchConnectionManager;
import com.liferay.portal.search.elasticsearch6.internal.index.IndexNameBuilder;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.time.StopWatch;

import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.search.SearchPhaseExecutionException;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.Suggest.Suggestion;
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry;
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestionBuilder;
import org.elasticsearch.search.suggest.term.TermSuggestion;

import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

/**
 * @author Michael C. Han
 */
@Component(
	immediate = true, property = "search.engine.impl=Elasticsearch",
	service = QuerySuggester.class
)
public class ElasticsearchQuerySuggester implements QuerySuggester {

	@Override
	public String spellCheckKeywords(SearchContext searchContext)
		throws SearchException {

		Suggester suggester = createSpellCheckSuggester(searchContext, 1);

		Suggest suggest = doSuggest(suggester, searchContext);

		if (suggest == null) {
			return StringPool.BLANK;
		}

		List words = new ArrayList<>();

		for (Suggest.Suggestion
				> suggestion :
						suggest) {

			for (Suggest.Suggestion.Entry
					
						suggestionEntry : suggestion) {

				Text text = getWord(suggestionEntry);

				words.add(text.string());
			}
		}

		return StringUtil.merge(words, StringPool.SPACE);
	}

	@Override
	public Map> spellCheckKeywords(
		SearchContext searchContext, int max) {

		Suggester suggester = createSpellCheckSuggester(searchContext, max);

		Suggest suggest = doSuggest(suggester, searchContext);

		if (suggest == null) {
			return new LinkedHashMap<>();
		}

		Map> suggestionsMap = new LinkedHashMap<>();

		for (Suggest.Suggestion
				> suggestion :
						suggest) {

			for (Suggest.Suggestion.Entry
					
						suggestionEntry : suggestion) {

				List suggestionsList = new ArrayList<>();

				for (Suggest.Suggestion.Entry.Option suggestionEntryOption :
						suggestionEntry.getOptions()) {

					Text optionText = suggestionEntryOption.getText();

					suggestionsList.add(optionText.string());
				}

				Text text = suggestionEntry.getText();

				suggestionsMap.put(text.string(), suggestionsList);
			}
		}

		return suggestionsMap;
	}

	@Override
	public SuggesterResults suggest(
		SearchContext searchContext, Suggester suggester) {

		Suggest suggest = doSuggest(suggester, searchContext);

		if (suggest == null) {
			return new SuggesterResults();
		}

		SuggesterResults suggesterResults = new SuggesterResults();

		for (Suggest.Suggestion
				> suggestion :
						suggest) {

			suggesterResults.addSuggesterResult(translate(suggestion));
		}

		return suggesterResults;
	}

	@Override
	public String[] suggestKeywordQueries(
		SearchContext searchContext, int max) {

		Suggester suggester = createQuerySuggester(searchContext, max);

		Suggest suggest = doSuggest(suggester, searchContext);

		if (suggest == null) {
			return StringPool.EMPTY_ARRAY;
		}

		Suggestion> suggestion =
			suggest.getSuggestion(suggester.getName());

		if (suggestion == null) {
			return StringPool.EMPTY_ARRAY;
		}

		List keywordQueries = new ArrayList<>();

		for (Suggest.Suggestion.Entry
				suggestionEntry : suggestion) {

			for (Suggest.Suggestion.Entry.Option suggestionEntryOption :
					suggestionEntry.getOptions()) {

				Text optionText = suggestionEntryOption.getText();

				keywordQueries.add(optionText.string());
			}
		}

		return keywordQueries.toArray(new String[keywordQueries.size()]);
	}

	protected PhraseSuggester createQuerySuggester(
		SearchContext searchContext, int max) {

		Localization localization = getLocalization();

		String field = localization.getLocalizedName(
			Field.KEYWORD_SEARCH, searchContext.getLanguageId());

		PhraseSuggester phraseSuggester = new PhraseSuggester(
			"keywordQueryRequest", field, searchContext.getKeywords());

		phraseSuggester.setSize(max);

		return phraseSuggester;
	}

	protected Suggester createSpellCheckSuggester(
		SearchContext searchContext, int max) {

		Localization localization = getLocalization();

		String field = localization.getLocalizedName(
			Field.SPELL_CHECK_WORD, searchContext.getLanguageId());

		TermSuggester termSuggester = new TermSuggester(
			"spellCheckRequest", field, searchContext.getKeywords());

		termSuggester.setSize(max);

		return termSuggester;
	}

	protected Suggest doSuggest(
		Suggester suggester, SearchContext searchContext) {

		StopWatch stopWatch = new StopWatch();

		stopWatch.start();

		Client client = elasticsearchConnectionManager.getClient();

		SuggestBuilder suggestBuilder = suggesterTranslator.translate(
			suggester, searchContext);

		if (suggester instanceof AggregateSuggester) {
			AggregateSuggester aggregateSuggester =
				(AggregateSuggester)suggester;

			suggestBuilder.setGlobalText(aggregateSuggester.getValue());
		}

		SearchRequestBuilder searchRequestBuilder = client.prepareSearch(
			indexNameBuilder.getIndexName(searchContext.getCompanyId()));

		Map> suggestionBuilders =
			suggestBuilder.getSuggestions();

		for (Map.Entry> entry :
				suggestionBuilders.entrySet()) {

			SuggestBuilder suggestBuilder2 = new SuggestBuilder();

			searchRequestBuilder.suggest(
				suggestBuilder2.addSuggestion(
					entry.getKey(), entry.getValue()));
		}

		SearchResponse suggestResponse = getSuggestResponse(
			searchRequestBuilder);

		if (suggestResponse == null) {
			return null;
		}

		Suggest suggest = suggestResponse.getSuggest();

		if (_log.isInfoEnabled()) {
			stopWatch.stop();

			_log.info(
				"Spell checked keywords in " + stopWatch.getTime() + "ms");
		}

		return suggest;
	}

	protected Localization getLocalization() {

		// See LPS-72507 and LPS-76500

		if (localization != null) {
			return localization;
		}

		return LocalizationUtil.getLocalization();
	}

	protected SearchResponse getSuggestResponse(
		SearchRequestBuilder searchRequestBuilder) {

		try {
			return searchRequestBuilder.get();
		}
		catch (SearchPhaseExecutionException spee) {
			ElasticsearchException ee = spee.guessRootCauses()[0];

			String message = ee.getMessage();

			if (message.startsWith("no mapping found for field")) {
				if (_log.isWarnEnabled()) {
					_log.warn("No dictionary indexed", ee);
				}

				return null;
			}

			throw spee;
		}
	}

	protected Text getWord(Entry suggestionEntry) {
		List suggestionEntryOptions =
			suggestionEntry.getOptions();

		if (suggestionEntryOptions.isEmpty()) {
			return suggestionEntry.getText();
		}

		Suggest.Suggestion.Entry.Option suggestionEntryOption =
			suggestionEntryOptions.get(0);

		return suggestionEntryOption.getText();
	}

	protected SuggesterResult translate(
		Suggest.Suggestion
			> suggestion) {

		SuggesterResult suggesterResult = new SuggesterResult(
			suggestion.getName());

		for (Suggest.Suggestion.Entry
				suggestionEntry : suggestion) {

			SuggesterResult.Entry suggesterResultEntry = translate(
				suggestionEntry);

			suggesterResult.addEntry(suggesterResultEntry);
		}

		return suggesterResult;
	}

	protected SuggesterResult.Entry.Option translate(
		Suggest.Suggestion.Entry.Option suggestionEntryOption) {

		Text text = suggestionEntryOption.getText();

		SuggesterResult.Entry.Option suggesterResultEntryOption =
			new SuggesterResult.Entry.Option(
				text.string(), suggestionEntryOption.getScore());

		Text highlighted = suggestionEntryOption.getHighlighted();

		if (suggestionEntryOption.getHighlighted() != null) {
			suggesterResultEntryOption.setHighlightedText(highlighted.string());
		}

		if (suggestionEntryOption instanceof TermSuggestion.Entry.Option) {
			TermSuggestion.Entry.Option termSuggestionEntryOption =
				(TermSuggestion.Entry.Option)suggestionEntryOption;

			suggesterResultEntryOption.setFrequency(
				termSuggestionEntryOption.getFreq());
		}

		return suggesterResultEntryOption;
	}

	protected SuggesterResult.Entry translate(
		Suggest.Suggestion.Entry
			suggestionEntry) {

		Text text = suggestionEntry.getText();

		SuggesterResult.Entry suggesterResultEntry = new SuggesterResult.Entry(
			text.string());

		List suggestionEntryOptions =
			suggestionEntry.getOptions();

		for (Suggest.Suggestion.Entry.Option suggestionEntryOption :
				suggestionEntryOptions) {

			SuggesterResult.Entry.Option suggesterResultEntryOption = translate(
				suggestionEntryOption);

			suggesterResultEntry.addOption(suggesterResultEntryOption);
		}

		return suggesterResultEntry;
	}

	@Reference(unbind = "-")
	protected ElasticsearchConnectionManager elasticsearchConnectionManager;

	@Reference(unbind = "-")
	protected IndexNameBuilder indexNameBuilder;

	protected Localization localization;

	@Reference(target = "(search.engine.impl=Elasticsearch)")
	protected SuggesterTranslator suggesterTranslator;

	private static final Log _log = LogFactoryUtil.getLog(
		ElasticsearchQuerySuggester.class);

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy