com.liferay.portal.kernel.search.BaseIndexer.java Source code

Java tutorial

Introduction

Here is the source code for com.liferay.portal.kernel.search.BaseIndexer.java

Source

/**
 * 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.kernel.search;

import com.liferay.asset.kernel.model.AssetEntry;
import com.liferay.expando.kernel.model.ExpandoBridge;
import com.liferay.petra.lang.HashUtil;
import com.liferay.petra.string.StringBundler;
import com.liferay.petra.string.StringPool;
import com.liferay.portal.kernel.exception.NoSuchCountryException;
import com.liferay.portal.kernel.exception.NoSuchModelException;
import com.liferay.portal.kernel.exception.NoSuchRegionException;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.language.LanguageUtil;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.model.Address;
import com.liferay.portal.kernel.model.BaseModel;
import com.liferay.portal.kernel.model.Country;
import com.liferay.portal.kernel.model.Group;
import com.liferay.portal.kernel.model.Region;
import com.liferay.portal.kernel.model.ResourcedModel;
import com.liferay.portal.kernel.model.WorkflowedModel;
import com.liferay.portal.kernel.search.facet.Facet;
import com.liferay.portal.kernel.search.facet.MultiValueFacet;
import com.liferay.portal.kernel.search.filter.BooleanFilter;
import com.liferay.portal.kernel.search.filter.Filter;
import com.liferay.portal.kernel.search.filter.QueryFilter;
import com.liferay.portal.kernel.search.filter.TermsFilter;
import com.liferay.portal.kernel.search.generic.BooleanQueryImpl;
import com.liferay.portal.kernel.search.generic.MatchAllQuery;
import com.liferay.portal.kernel.search.hits.HitsProcessorRegistryUtil;
import com.liferay.portal.kernel.security.auth.CompanyThreadLocal;
import com.liferay.portal.kernel.security.permission.PermissionChecker;
import com.liferay.portal.kernel.security.permission.PermissionThreadLocal;
import com.liferay.portal.kernel.service.CountryServiceUtil;
import com.liferay.portal.kernel.service.GroupLocalServiceUtil;
import com.liferay.portal.kernel.service.RegionServiceUtil;
import com.liferay.portal.kernel.util.ArrayUtil;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.ListUtil;
import com.liferay.portal.kernel.util.LocaleUtil;
import com.liferay.portal.kernel.util.LocalizationUtil;
import com.liferay.portal.kernel.util.PropsKeys;
import com.liferay.portal.kernel.util.PropsUtil;
import com.liferay.portal.kernel.util.ServiceProxyFactory;
import com.liferay.portal.kernel.util.SetUtil;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.kernel.workflow.WorkflowConstants;
import com.liferay.registry.collections.ServiceTrackerCollections;

import java.io.Serializable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;

/**
 * @author Brian Wing Shun Chan
 * @author Hugo Huijser
 * @author Ryan Park
 * @author Raymond Aug
 */
public abstract class BaseIndexer<T> implements Indexer<T> {

    @Override
    public void delete(long companyId, String uid) throws SearchException {
        try {
            IndexWriterHelperUtil.deleteDocument(getSearchEngineId(), companyId, uid, _commitImmediately);
        } catch (SearchException se) {
            throw se;
        } catch (Exception e) {
            throw new SearchException(e);
        }
    }

    @Override
    public void delete(T object) throws SearchException {
        if (object == null) {
            return;
        }

        try {
            doDelete(object);
        } catch (SearchException se) {
            throw se;
        } catch (Exception e) {
            throw new SearchException(e);
        }
    }

    @Override
    public boolean equals(Object object) {
        if (object == this) {
            return true;
        }

        if (!(object instanceof Indexer<?>)) {
            return false;
        }

        Indexer<?> indexer = (Indexer<?>) object;

        return Objects.equals(getClassName(), indexer.getClassName());
    }

    @Override
    public Document getDocument(T object) throws SearchException {
        try {
            Document document = doGetDocument(object);

            for (IndexerPostProcessor indexerPostProcessor : _indexerPostProcessors) {

                indexerPostProcessor.postProcessDocument(document, object);
            }

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

            Map<String, Field> fields = document.getFields();

            Field groupIdField = fields.get(Field.GROUP_ID);

            if (groupIdField != null) {
                long groupId = GetterUtil.getLong(groupIdField.getValue());

                addStagingGroupKeyword(document, groupId);
            }

            return document;
        } catch (SearchException se) {
            throw se;
        } catch (Exception e) {
            throw new SearchException(e);
        }
    }

    @Override
    public BooleanFilter getFacetBooleanFilter(String className, SearchContext searchContext) throws Exception {

        return null;
    }

    @Override
    public BooleanQuery getFullQuery(SearchContext searchContext) throws SearchException {

        try {
            searchContext.setSearchEngineId(getSearchEngineId());

            resetFullQuery(searchContext);

            String[] fullQueryEntryClassNames = searchContext.getFullQueryEntryClassNames();

            if (ArrayUtil.isNotEmpty(fullQueryEntryClassNames)) {
                searchContext.setAttribute("relatedEntryClassNames", getSearchClassNames());
            }

            String[] entryClassNames = ArrayUtil.append(getSearchClassNames(), fullQueryEntryClassNames);

            searchContext.setEntryClassNames(entryClassNames);

            BooleanFilter fullQueryBooleanFilter = new BooleanFilter();

            addSearchAssetCategoryIds(fullQueryBooleanFilter, searchContext);
            addSearchAssetTagNames(fullQueryBooleanFilter, searchContext);
            addSearchFolderId(fullQueryBooleanFilter, searchContext);
            addSearchLayout(fullQueryBooleanFilter, searchContext);
            addSearchUserId(fullQueryBooleanFilter, searchContext);

            Map<String, Indexer<?>> entryClassNameIndexerMap = _getEntryClassNameIndexerMap(entryClassNames,
                    searchContext.getSearchEngineId());

            _addPreFilters(fullQueryBooleanFilter, entryClassNameIndexerMap, searchContext);

            BooleanQuery fullQuery = createFullQuery(fullQueryBooleanFilter, searchContext);

            fullQuery.setQueryConfig(searchContext.getQueryConfig());

            return fullQuery;
        } catch (SearchException se) {
            throw se;
        } catch (Exception e) {
            throw new SearchException(e);
        }
    }

    @Override
    public IndexerPostProcessor[] getIndexerPostProcessors() {
        return _indexerPostProcessors;
    }

    @Override
    public String[] getSearchClassNames() {
        return new String[] { getClassName() };
    }

    @Override
    public String getSearchEngineId() {
        if (_searchEngineId != null) {
            return _searchEngineId;
        }

        Class<?> clazz = getClass();

        String searchEngineId = GetterUtil.getString(PropsUtil.get(PropsKeys.INDEX_SEARCH_ENGINE_ID,
                new com.liferay.portal.kernel.configuration.Filter(clazz.getName())));

        if (Validator.isNotNull(searchEngineId)) {
            SearchEngine searchEngine = SearchEngineHelperUtil.getSearchEngine(searchEngineId);

            if (searchEngine != null) {
                _searchEngineId = searchEngineId;
            }
        }

        if (_searchEngineId == null) {
            _searchEngineId = SearchEngineHelperUtil.getDefaultSearchEngineId();
        }

        if (_log.isDebugEnabled()) {
            _log.debug(StringBundler.concat("Search engine ID for ", clazz.getName(), " is ", searchEngineId));
        }

        return _searchEngineId;
    }

    /**
     * @deprecated As of Judson (7.1.x), replaced by {@link
     *             com.liferay.portal.search.sort.SortFieldBuilder
     *             #getSortField}
     */
    @Deprecated
    @Override
    public String getSortField(String orderByCol) {
        String sortField = doGetSortField(orderByCol);

        if (_document.isDocumentSortableTextField(sortField)) {
            return Field.getSortableFieldName(sortField);
        }

        return sortField;
    }

    @Override
    public Summary getSummary(Document document, String snippet, PortletRequest portletRequest,
            PortletResponse portletResponse) throws SearchException {

        try {
            Locale locale = getLocale(portletRequest);

            Summary summary = doGetSummary(document, locale, snippet, portletRequest, portletResponse);

            for (IndexerPostProcessor indexerPostProcessor : _indexerPostProcessors) {

                indexerPostProcessor.postProcessSummary(summary, document, locale, snippet);
            }

            return summary;
        } catch (SearchException se) {
            throw se;
        } catch (Exception e) {
            throw new SearchException(e);
        }
    }

    @Override
    public int hashCode() {
        return HashUtil.hash(0, getClassName());
    }

    @Override
    public boolean hasPermission(PermissionChecker permissionChecker, String entryClassName, long entryClassPK,
            String actionId) throws Exception {

        return true;
    }

    @Override
    public boolean isCommitImmediately() {
        return _commitImmediately;
    }

    @Override
    public boolean isFilterSearch() {
        return _filterSearch;
    }

    @Override
    public boolean isIndexerEnabled() {
        if (_indexerEnabled == null) {
            String indexerEnabled = PropsUtil.get(PropsKeys.INDEXER_ENABLED,
                    new com.liferay.portal.kernel.configuration.Filter(getClassName()));

            _indexerEnabled = GetterUtil.getBoolean(indexerEnabled, true);

            return _indexerEnabled;
        }

        return _indexerEnabled;
    }

    @Override
    public boolean isPermissionAware() {
        return _permissionAware;
    }

    public boolean isSelectAllLocales() {
        return _selectAllLocales;
    }

    @Override
    public boolean isStagingAware() {
        return _stagingAware;
    }

    @Override
    public boolean isVisible(long classPK, int status) throws Exception {
        return true;
    }

    @Override
    public void postProcessContextBooleanFilter(BooleanFilter contextBooleanFilter, SearchContext searchContext)
            throws Exception {
    }

    /**
     * @deprecated As of Wilberforce (7.0.x), replaced by {@link
     *             #postProcessContextBooleanFilter(BooleanFilter,
     *             SearchContext)}
     */
    @Deprecated
    @Override
    public void postProcessContextQuery(BooleanQuery contextQuery, SearchContext searchContext) throws Exception {
    }

    @Override
    public void postProcessSearchQuery(BooleanQuery searchQuery, BooleanFilter fullQueryBooleanFilter,
            SearchContext searchContext) throws Exception {

        if (Validator.isNull(searchContext.getKeywords())) {
            addSearchTerm(searchQuery, searchContext, Field.DESCRIPTION, false);
            addSearchTerm(searchQuery, searchContext, Field.TITLE, false);
            addSearchTerm(searchQuery, searchContext, Field.USER_NAME, false);
        }
    }

    /**
     * @deprecated As of Wilberforce (7.0.x), replaced by {@link
     *             #postProcessSearchQuery(BooleanQuery, BooleanFilter,
     *             SearchContext)}
     */
    @Deprecated
    @Override
    public void postProcessSearchQuery(BooleanQuery searchQuery, SearchContext searchContext) throws Exception {
    }

    @Override
    public void registerIndexerPostProcessor(IndexerPostProcessor indexerPostProcessor) {

        List<IndexerPostProcessor> indexerPostProcessorsList = ListUtil.fromArray(_indexerPostProcessors);

        indexerPostProcessorsList.add(indexerPostProcessor);

        _indexerPostProcessors = indexerPostProcessorsList.toArray(new IndexerPostProcessor[0]);
    }

    @Override
    public void reindex(Collection<T> collection) {
        if (IndexWriterHelperUtil.isIndexReadOnly() || IndexWriterHelperUtil.isIndexReadOnly(getClassName())
                || !isIndexerEnabled() || collection.isEmpty()) {

            return;
        }

        for (T element : collection) {
            try {
                reindex(element);
            } catch (SearchException se) {
                _log.error("Unable to index object: " + element, se);
            }
        }
    }

    @Override
    public void reindex(String className, long classPK) throws SearchException {
        try {
            if (IndexWriterHelperUtil.isIndexReadOnly() || IndexWriterHelperUtil.isIndexReadOnly(getClassName())
                    || !isIndexerEnabled() || (classPK <= 0)) {

                return;
            }

            doReindex(className, classPK);
        } catch (NoSuchModelException nsme) {
            if (_log.isWarnEnabled()) {
                _log.warn(StringBundler.concat("Unable to index ", className, " ", classPK), nsme);
            }
        } catch (SearchException se) {
            throw se;
        } catch (Exception e) {
            throw new SearchException(e);
        }
    }

    @Override
    public void reindex(String[] ids) throws SearchException {
        long companyThreadLocalCompanyId = CompanyThreadLocal.getCompanyId();

        try {
            if (IndexWriterHelperUtil.isIndexReadOnly() || IndexWriterHelperUtil.isIndexReadOnly(getClassName())
                    || !isIndexerEnabled()) {

                return;
            }

            if (ids.length > 0) {
                long companyId = GetterUtil.getLong(ids[0]);

                CompanyThreadLocal.setCompanyId(companyId);
            }

            doReindex(ids);
        } catch (SearchException se) {
            throw se;
        } catch (Exception e) {
            throw new SearchException(e);
        } finally {
            CompanyThreadLocal.setCompanyId(companyThreadLocalCompanyId);
        }
    }

    @Override
    public void reindex(T object) throws SearchException {
        try {
            if (IndexWriterHelperUtil.isIndexReadOnly() || IndexWriterHelperUtil.isIndexReadOnly(getClassName())
                    || !isIndexerEnabled()) {

                return;
            }

            if (object == null) {
                return;
            }

            doReindex(object);
        } catch (SearchException se) {
            throw se;
        } catch (Exception e) {
            throw new SearchException(e);
        }
    }

    @Override
    public Hits search(SearchContext searchContext) throws SearchException {
        try {
            Hits hits = null;

            QueryConfig queryConfig = searchContext.getQueryConfig();

            addDefaultHighlightFieldNames(queryConfig);

            if (ArrayUtil.isEmpty(queryConfig.getSelectedFieldNames())) {
                addDefaultSelectedFieldNames(searchContext);
            }

            addFacetSelectedFieldNames(searchContext, queryConfig);

            PermissionChecker permissionChecker = PermissionThreadLocal.getPermissionChecker();

            if ((permissionChecker != null) && isUseSearchResultPermissionFilter(searchContext)) {

                if (searchContext.getUserId() == 0) {
                    searchContext.setUserId(permissionChecker.getUserId());
                }

                SearchResultPermissionFilter searchResultPermissionFilter = _searchResultPermissionFilterFactory
                        .create(this::doSearch, permissionChecker);

                hits = searchResultPermissionFilter.search(searchContext);
            } else {
                hits = doSearch(searchContext);
            }

            processHits(searchContext, hits);

            return hits;
        } catch (SearchException se) {
            throw se;
        } catch (Exception e) {
            throw new SearchException(e);
        }
    }

    @Override
    public Hits search(SearchContext searchContext, String... selectedFieldNames) throws SearchException {

        QueryConfig queryConfig = searchContext.getQueryConfig();

        queryConfig.setSelectedFieldNames(selectedFieldNames);

        return search(searchContext);
    }

    @Override
    public long searchCount(SearchContext searchContext) throws SearchException {

        PermissionChecker permissionChecker = PermissionThreadLocal.getPermissionChecker();

        if ((permissionChecker != null) && isUseSearchResultPermissionFilter(searchContext)) {

            Hits hits = search(searchContext);

            return hits.getLength();
        }

        QueryConfig queryConfig = searchContext.getQueryConfig();

        queryConfig.setHighlightEnabled(false);
        queryConfig.setHitsProcessingEnabled(false);
        queryConfig.setScoreEnabled(false);
        queryConfig.setQueryIndexingEnabled(false);
        queryConfig.setQuerySuggestionEnabled(false);

        searchContext.setSearchEngineId(getSearchEngineId());

        BooleanQuery fullQuery = getFullQuery(searchContext);

        fullQuery.setQueryConfig(queryConfig);

        return IndexSearcherHelperUtil.searchCount(searchContext, fullQuery);
    }

    public void setCommitImmediately(boolean commitImmediately) {
        _commitImmediately = commitImmediately;
    }

    @Override
    public void setIndexerEnabled(boolean indexerEnabled) {
        _indexerEnabled = indexerEnabled;
    }

    public void setSelectAllLocales(boolean selectAllLocales) {
        _selectAllLocales = selectAllLocales;
    }

    @Override
    public void unregisterIndexerPostProcessor(IndexerPostProcessor indexerPostProcessor) {

        List<IndexerPostProcessor> indexerPostProcessorsList = ListUtil.fromArray(_indexerPostProcessors);

        indexerPostProcessorsList.remove(indexerPostProcessor);

        _indexerPostProcessors = indexerPostProcessorsList.toArray(new IndexerPostProcessor[0]);
    }

    protected void addDefaultHighlightFieldNames(QueryConfig queryConfig) {
        queryConfig.addHighlightFieldNames(Field.ASSET_CATEGORY_TITLES);

        if (queryConfig.isHighlightEnabled()) {
            queryConfig.addHighlightFieldNames(Field.CONTENT, Field.DESCRIPTION, Field.TITLE);
        }
    }

    protected void addDefaultSelectedFieldNames(SearchContext searchContext) {
        QueryConfig queryConfig = searchContext.getQueryConfig();

        Set<String> selectedFieldNames = null;

        if (!ArrayUtil.isEmpty(getDefaultSelectedFieldNames())) {
            selectedFieldNames = SetUtil.fromArray(getDefaultSelectedFieldNames());

            if (searchContext.isIncludeAttachments() || searchContext.isIncludeDiscussions()) {

                selectedFieldNames.add(Field.CLASS_NAME_ID);
                selectedFieldNames.add(Field.CLASS_PK);
            }
        }

        if (!ArrayUtil.isEmpty(getDefaultSelectedLocalizedFieldNames())) {
            if (selectedFieldNames == null) {
                selectedFieldNames = new HashSet<>();
            }

            if (isSelectAllLocales()) {
                addSelectedLocalizedFieldNames(selectedFieldNames,
                        LocaleUtil.toLanguageIds(LanguageUtil.getSupportedLocales()));
            } else {
                addSelectedLocalizedFieldNames(selectedFieldNames,
                        LocaleUtil.toLanguageId(queryConfig.getLocale()));
            }
        }

        if ((selectedFieldNames != null) && !selectedFieldNames.isEmpty()) {
            queryConfig.setSelectedFieldNames(selectedFieldNames.toArray(new String[0]));
        }
    }

    /**
     * @deprecated As of Wilberforce (7.0.x)
     */
    @Deprecated
    protected void addFacetClause(SearchContext searchContext, BooleanFilter facetBooleanFilter,
            Collection<Facet> facets) throws ParseException {

        BooleanQuery facetBooleanQuery = new BooleanQueryImpl();

        for (Facet facet : facets) {
            BooleanClause<Query> facetBooleanClause = facet.getFacetClause();

            if (facetBooleanClause != null) {
                facetBooleanQuery.add(facetBooleanClause.getClause(), facetBooleanClause.getBooleanClauseOccur());
            }
        }

        if (!facetBooleanQuery.hasClauses()) {
            return;
        }

        QueryFilter queryFilter = new QueryFilter(facetBooleanQuery);

        facetBooleanFilter.add(queryFilter, BooleanClauseOccur.MUST);
    }

    protected void addFacetSelectedFieldNames(SearchContext searchContext, QueryConfig queryConfig) {

        String[] selectedFieldNames = queryConfig.getSelectedFieldNames();

        if (ArrayUtil.isEmpty(selectedFieldNames)
                || ((selectedFieldNames.length == 1) && selectedFieldNames[0].equals(Field.ANY))) {

            return;
        }

        Set<String> selectedFieldNameSet = SetUtil.fromArray(selectedFieldNames);

        Map<String, Facet> facets = searchContext.getFacets();

        selectedFieldNameSet.addAll(facets.keySet());

        selectedFieldNames = selectedFieldNameSet.toArray(new String[0]);

        queryConfig.setSelectedFieldNames(selectedFieldNames);
    }

    protected void addLocalizedField(Document document, String field, Locale siteDefaultLocale,
            Map<Locale, String> map) {

        for (Map.Entry<Locale, String> entry : map.entrySet()) {
            Locale locale = entry.getKey();

            if (locale.equals(siteDefaultLocale)) {
                document.addText(field, entry.getValue());
            }

            String languageId = LocaleUtil.toLanguageId(locale);

            document.addText(LocalizationUtil.getLocalizedName(field, languageId), entry.getValue());
        }
    }

    protected void addSearchAssetCategoryIds(BooleanFilter queryBooleanFilter, SearchContext searchContext)
            throws Exception {

        MultiValueFacet multiValueFacet = new MultiValueFacet(searchContext);

        multiValueFacet.setFieldName(Field.ASSET_CATEGORY_IDS);
        multiValueFacet.setStatic(true);
        multiValueFacet.setValues(searchContext.getAssetCategoryIds());

        searchContext.addFacet(multiValueFacet);
    }

    protected void addSearchAssetTagNames(BooleanFilter queryBooleanFilter, SearchContext searchContext)
            throws Exception {

        MultiValueFacet multiValueFacet = new MultiValueFacet(searchContext);

        multiValueFacet.setFieldName(Field.ASSET_TAG_NAMES);
        multiValueFacet.setStatic(true);
        multiValueFacet.setValues(searchContext.getAssetTagNames());

        searchContext.addFacet(multiValueFacet);
    }

    protected Filter addSearchClassTypeIds(BooleanFilter contextBooleanFilter, SearchContext searchContext)
            throws Exception {

        long[] classTypeIds = searchContext.getClassTypeIds();

        if (ArrayUtil.isEmpty(classTypeIds)) {
            return null;
        }

        TermsFilter classTypeIdsTermsFilter = new TermsFilter(Field.CLASS_TYPE_ID);

        classTypeIdsTermsFilter.addValues(ArrayUtil.toStringArray(classTypeIds));

        return contextBooleanFilter.add(classTypeIdsTermsFilter, BooleanClauseOccur.MUST);
    }

    protected void addSearchEntryClassNames(BooleanFilter queryBooleanFilter, SearchContext searchContext)
            throws Exception {
    }

    protected Map<String, Query> addSearchExpando(BooleanQuery searchQuery, SearchContext searchContext,
            String keywords) throws Exception {

        _expandoQueryContributor.contribute(keywords, searchQuery, getSearchClassNames(), searchContext);

        return new HashMap<>();
    }

    protected void addSearchFolderId(BooleanFilter queryBooleanFilter, SearchContext searchContext)
            throws Exception {

        MultiValueFacet multiValueFacet = new MultiValueFacet(searchContext);

        multiValueFacet.setFieldName(Field.TREE_PATH);
        multiValueFacet.setStatic(true);

        long[] folderIds = searchContext.getFolderIds();

        if (ArrayUtil.isNotEmpty(folderIds)) {
            folderIds = ArrayUtil.remove(folderIds, _DEFAULT_FOLDER_ID);

            multiValueFacet.setValues(folderIds);
        }

        searchContext.addFacet(multiValueFacet);
    }

    protected Map<String, Query> addSearchKeywords(BooleanQuery searchQuery, SearchContext searchContext)
            throws Exception {

        return addSearchExpando(searchQuery, searchContext, searchContext.getKeywords());
    }

    protected void addSearchLayout(BooleanFilter queryBooleanFilter, SearchContext searchContext) throws Exception {

        MultiValueFacet multiValueFacet = new MultiValueFacet(searchContext);

        multiValueFacet.setFieldName(Field.LAYOUT_UUID);
        multiValueFacet.setStatic(true);

        searchContext.addFacet(multiValueFacet);
    }

    protected Map<String, Query> addSearchLocalizedTerm(BooleanQuery searchQuery, SearchContext searchContext,
            String field, boolean like) throws Exception {

        Map<String, Query> queries = new HashMap<>();

        queries.put(field, addSearchTerm(searchQuery, searchContext, field, like));

        String localizedFieldName = Field.getLocalizedName(searchContext.getLocale(), field);

        Query localizedQuery = addSearchTerm(searchQuery, searchContext, localizedFieldName, like);

        queries.put(localizedFieldName, localizedQuery);

        return queries;
    }

    protected Query addSearchTerm(BooleanQuery searchQuery, SearchContext searchContext, String field, boolean like)
            throws Exception {

        if (Validator.isNull(field)) {
            return null;
        }

        String value = null;

        Serializable serializable = searchContext.getAttribute(field);

        if (serializable != null) {
            Class<?> clazz = serializable.getClass();

            if (clazz.isArray()) {
                value = StringUtil.merge((Object[]) serializable);
            } else {
                value = GetterUtil.getString(serializable);
            }
        } else {
            value = GetterUtil.getString(serializable);
        }

        if (Validator.isNotNull(value) && (searchContext.getFacet(field) != null)) {

            return null;
        }

        if (Validator.isNull(value)) {
            value = searchContext.getKeywords();
        }

        if (Validator.isNull(value)) {
            return null;
        }

        Query query = null;

        if (searchContext.isAndSearch()) {
            query = searchQuery.addRequiredTerm(field, value, like);
        } else {
            query = searchQuery.addTerm(field, value, like);
        }

        return query;
    }

    protected void addSearchUserId(BooleanFilter queryBooleanFilter, SearchContext searchContext) throws Exception {

        MultiValueFacet multiValueFacet = new MultiValueFacet(searchContext);

        multiValueFacet.setFieldName(Field.USER_ID);
        multiValueFacet.setStatic(true);

        long userId = GetterUtil.getLong(searchContext.getAttribute(Field.USER_ID));

        if (userId > 0) {
            multiValueFacet.setValues(new long[] { userId });
        }

        searchContext.addFacet(multiValueFacet);
    }

    protected void addSelectedLocalizedFieldNames(Set<String> selectedFieldNames, String... languageIds) {

        for (String defaultLocalizedSelectedFieldName : getDefaultSelectedLocalizedFieldNames()) {

            selectedFieldNames.add(defaultLocalizedSelectedFieldName);

            for (String languageId : languageIds) {
                String localizedFieldName = LocalizationUtil.getLocalizedName(defaultLocalizedSelectedFieldName,
                        languageId);

                selectedFieldNames.add(localizedFieldName);
            }
        }
    }

    protected void addStagingGroupKeyword(Document document, long groupId) {
        if (!isStagingAware()) {
            return;
        }

        document.addKeyword(Field.STAGING_GROUP, isStagingGroup(groupId));
    }

    protected void addStatus(BooleanFilter contextBooleanFilter, SearchContext searchContext) throws Exception {

        int[] statuses = GetterUtil.getIntegerValues(searchContext.getAttribute(Field.STATUS), null);

        if (ArrayUtil.isEmpty(statuses)) {
            int status = GetterUtil.getInteger(searchContext.getAttribute(Field.STATUS),
                    WorkflowConstants.STATUS_APPROVED);

            statuses = new int[] { status };
        }

        if (!ArrayUtil.contains(statuses, WorkflowConstants.STATUS_ANY)) {
            TermsFilter statusesTermsFilter = new TermsFilter(Field.STATUS);

            statusesTermsFilter.addValues(ArrayUtil.toStringArray(statuses));

            contextBooleanFilter.add(statusesTermsFilter, BooleanClauseOccur.MUST);
        } else {
            contextBooleanFilter.addTerm(Field.STATUS, String.valueOf(WorkflowConstants.STATUS_IN_TRASH),
                    BooleanClauseOccur.MUST_NOT);
        }
    }

    protected BooleanQuery createFullQuery(BooleanFilter fullQueryBooleanFilter, SearchContext searchContext)
            throws Exception {

        BooleanQuery searchQuery = new BooleanQueryImpl();

        addSearchKeywords(searchQuery, searchContext);

        _addSearchTerms(searchQuery, fullQueryBooleanFilter, searchContext);

        doPostProcessSearchQuery(this, searchQuery, searchContext);

        Map<String, Facet> facets = searchContext.getFacets();

        BooleanFilter facetBooleanFilter = new BooleanFilter();

        addFacetClause(searchContext, facetBooleanFilter, facets.values());

        if (facetBooleanFilter.hasClauses()) {
            fullQueryBooleanFilter.add(facetBooleanFilter, BooleanClauseOccur.MUST);
        }

        BooleanQuery fullBooleanQuery = new BooleanQueryImpl();

        if (fullQueryBooleanFilter.hasClauses()) {
            fullBooleanQuery.setPreBooleanFilter(fullQueryBooleanFilter);
        }

        if (searchQuery.hasClauses()) {
            fullBooleanQuery.add(searchQuery, BooleanClauseOccur.MUST);
        }

        BooleanClause<Query>[] booleanClauses = searchContext.getBooleanClauses();

        if (booleanClauses != null) {
            for (BooleanClause<Query> booleanClause : booleanClauses) {
                fullBooleanQuery.add(booleanClause.getClause(), booleanClause.getBooleanClauseOccur());
            }
        }

        postProcessFullQuery(fullBooleanQuery, searchContext);

        for (IndexerPostProcessor indexerPostProcessor : _indexerPostProcessors) {

            indexerPostProcessor.postProcessFullQuery(fullBooleanQuery, searchContext);
        }

        return fullBooleanQuery;
    }

    protected Summary createSummary(Document document) {
        return createSummary(document, Field.TITLE, Field.CONTENT);
    }

    protected Summary createSummary(Document document, String titleField, String contentField) {

        String prefix = Field.SNIPPET + StringPool.UNDERLINE;

        String title = document.get(prefix + titleField, titleField);
        String content = document.get(prefix + contentField, contentField);

        return new Summary(title, content);
    }

    protected void deleteDocument(long companyId, long field1) throws Exception {

        deleteDocument(companyId, String.valueOf(field1));
    }

    protected void deleteDocument(long companyId, long field1, String field2) throws Exception {

        deleteDocument(companyId, String.valueOf(field1), field2);
    }

    protected void deleteDocument(long companyId, String field1) throws Exception {

        Document document = new DocumentImpl();

        document.addUID(getClassName(), field1);

        IndexWriterHelperUtil.deleteDocument(getSearchEngineId(), companyId, document.get(Field.UID),
                _commitImmediately);
    }

    protected void deleteDocument(long companyId, String field1, String field2) throws Exception {

        Document document = new DocumentImpl();

        document.addUID(getClassName(), field1, field2);

        IndexWriterHelperUtil.deleteDocument(getSearchEngineId(), companyId, document.get(Field.UID),
                _commitImmediately);
    }

    protected abstract void doDelete(T object) throws Exception;

    protected abstract Document doGetDocument(T object) throws Exception;

    /**
     * @deprecated As of Judson (7.1.x), replaced by {@link
     *             com.liferay.portal.search.contributor.sort.SortFieldTranslator}
     */
    @Deprecated
    protected String doGetSortField(String orderByCol) {
        return orderByCol;
    }

    protected abstract Summary doGetSummary(Document document, Locale locale, String snippet,
            PortletRequest portletRequest, PortletResponse portletResponse) throws Exception;

    /**
     * @deprecated As of Wilberforce (7.0.x), added strictly to support
     *             backwards compatibility of {@link
     *             Indexer#postProcessSearchQuery(BooleanQuery, SearchContext)}
     */
    @Deprecated
    protected void doPostProcessSearchQuery(Indexer<?> indexer, BooleanQuery searchQuery,
            SearchContext searchContext) throws Exception {

        indexer.postProcessSearchQuery(searchQuery, searchContext);
    }

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

    protected abstract void doReindex(String[] ids) throws Exception;

    protected abstract void doReindex(T object) throws Exception;

    protected Hits doSearch(SearchContext searchContext) throws SearchException {

        searchContext.setSearchEngineId(getSearchEngineId());

        Query fullQuery = getFullQuery(searchContext);

        if (!fullQuery.hasChildren()) {
            BooleanFilter preBooleanFilter = fullQuery.getPreBooleanFilter();

            fullQuery = new MatchAllQuery();

            fullQuery.setPreBooleanFilter(preBooleanFilter);
        }

        fullQuery.setQueryConfig(searchContext.getQueryConfig());

        return IndexSearcherHelperUtil.search(searchContext, fullQuery);
    }

    protected Document getBaseModelDocument(String portletId, BaseModel<?> baseModel) {

        return getBaseModelDocument(portletId, baseModel, baseModel);
    }

    protected Document getBaseModelDocument(String portletId, BaseModel<?> baseModel,
            BaseModel<?> workflowedBaseModel) {

        Document document = newDocument();

        String className = baseModel.getModelClassName();

        long classPK = 0;
        long resourcePrimKey = 0;

        if (baseModel instanceof ResourcedModel) {
            ResourcedModel resourcedModel = (ResourcedModel) baseModel;

            classPK = resourcedModel.getResourcePrimKey();
            resourcePrimKey = resourcedModel.getResourcePrimKey();
        } else {
            classPK = (Long) baseModel.getPrimaryKeyObj();
        }

        DocumentHelper documentHelper = new DocumentHelper(document);

        documentHelper.setEntryKey(className, classPK);

        document.addUID(className, classPK);

        if (resourcePrimKey > 0) {
            document.addKeyword(Field.ROOT_ENTRY_CLASS_PK, resourcePrimKey);
        }

        if (workflowedBaseModel instanceof WorkflowedModel) {
            WorkflowedModel workflowedModel = (WorkflowedModel) workflowedBaseModel;

            document.addKeyword(Field.STATUS, workflowedModel.getStatus());
        }

        for (DocumentContributor documentContributor : getDocumentContributors()) {

            documentContributor.contribute(document, baseModel);
        }

        return document;
    }

    protected String getClassName(SearchContext searchContext) {
        return getClassName();
    }

    protected String[] getDefaultSelectedFieldNames() {
        return _defaultSelectedFieldNames;
    }

    protected String[] getDefaultSelectedLocalizedFieldNames() {
        return _defaultSelectedLocalizedFieldNames;
    }

    protected List<DocumentContributor> getDocumentContributors() {
        if (_documentContributors != null) {
            return _documentContributors;
        }

        _documentContributors = ServiceTrackerCollections.openList(DocumentContributor.class);

        return _documentContributors;
    }

    protected String getExpandoFieldName(SearchContext searchContext, ExpandoBridge expandoBridge,
            String attributeName) {

        return null;
    }

    protected List<ExpandoQueryContributor> getExpandoQueryContributors() {
        return Collections.singletonList(_expandoQueryContributor);
    }

    protected Locale getLocale(PortletRequest portletRequest) {
        if (portletRequest != null) {
            return portletRequest.getLocale();
        }

        return LocaleUtil.getMostRelevantLocale();
    }

    protected Set<String> getLocalizedCountryNames(Country country) {
        Set<String> countryNames = new HashSet<>();

        for (Locale locale : LanguageUtil.getAvailableLocales()) {
            String countryName = country.getName(locale);

            countryName = StringUtil.toLowerCase(countryName);

            countryNames.add(countryName);
        }

        return countryNames;
    }

    protected Group getSiteGroup(long groupId) {
        Group group = null;

        try {
            group = GroupLocalServiceUtil.getGroup(groupId);

            if (group.isLayout()) {
                group = group.getParentGroup();
            }
        } catch (PortalException pe) {
            if (_log.isDebugEnabled()) {
                _log.debug("Unable to get site group", pe);
            }
        }

        return group;
    }

    protected long getSiteGroupId(long groupId) {
        Group group = getSiteGroup(groupId);

        if (group == null) {
            return groupId;
        }

        return group.getGroupId();
    }

    protected Locale getSnippetLocale(Document document, Locale locale) {
        String prefix = Field.SNIPPET + StringPool.UNDERLINE;

        String localizedAssetCategoryTitlesName = prefix
                + Field.getLocalizedName(locale, Field.ASSET_CATEGORY_TITLES);
        String localizedContentName = prefix + Field.getLocalizedName(locale, Field.CONTENT);
        String localizedDescriptionName = prefix + Field.getLocalizedName(locale, Field.DESCRIPTION);
        String localizedTitleName = prefix + Field.getLocalizedName(locale, Field.TITLE);

        if ((document.getField(localizedAssetCategoryTitlesName) != null)
                || (document.getField(localizedContentName) != null)
                || (document.getField(localizedDescriptionName) != null)
                || (document.getField(localizedTitleName) != null)) {

            return locale;
        }

        return null;
    }

    protected boolean isStagingGroup(long groupId) {
        Group group = getSiteGroup(groupId);

        if (group == null) {
            return false;
        }

        return group.isStagingGroup();
    }

    protected boolean isUseSearchResultPermissionFilter(SearchContext searchContext) {

        return isFilterSearch();
    }

    protected boolean isVisible(int entryStatus, int queryStatus) {
        if (((queryStatus != WorkflowConstants.STATUS_ANY) && (entryStatus == queryStatus))
                || (entryStatus != WorkflowConstants.STATUS_IN_TRASH)) {

            return true;
        }

        return false;
    }

    protected Document newDocument() {
        return (Document) _document.clone();
    }

    protected void populateAddresses(Document document, List<Address> addresses, long regionId, long countryId)
            throws PortalException {

        List<String> cities = new ArrayList<>();

        List<String> countries = new ArrayList<>();

        if (countryId > 0) {
            try {
                countries.addAll(getLocalizedCountryNames(CountryServiceUtil.getCountry(countryId)));
            } catch (NoSuchCountryException nsce) {
                if (_log.isWarnEnabled()) {
                    _log.warn(nsce.getMessage());
                }
            }
        }

        List<String> regions = new ArrayList<>();

        if (regionId > 0) {
            try {
                Region region = RegionServiceUtil.getRegion(regionId);

                regions.add(StringUtil.toLowerCase(region.getName()));
            } catch (NoSuchRegionException nsre) {
                if (_log.isWarnEnabled()) {
                    _log.warn(nsre.getMessage());
                }
            }
        }

        List<String> streets = new ArrayList<>();
        List<String> zips = new ArrayList<>();

        for (Address address : addresses) {
            cities.add(StringUtil.toLowerCase(address.getCity()));
            countries.addAll(getLocalizedCountryNames(address.getCountry()));

            Region region = address.getRegion();

            regions.add(StringUtil.toLowerCase(region.getName()));

            streets.add(StringUtil.toLowerCase(address.getStreet1()));
            streets.add(StringUtil.toLowerCase(address.getStreet2()));
            streets.add(StringUtil.toLowerCase(address.getStreet3()));
            zips.add(StringUtil.toLowerCase(address.getZip()));
        }

        document.addText("city", cities.toArray(new String[0]));
        document.addText("country", countries.toArray(new String[0]));
        document.addText("region", regions.toArray(new String[0]));
        document.addText("street", streets.toArray(new String[0]));
        document.addText("zip", zips.toArray(new String[0]));
    }

    protected Map<Locale, String> populateMap(AssetEntry assetEntry, Map<Locale, String> map) {

        return LocalizationUtil.populateLocalizationMap(map, assetEntry.getDefaultLanguageId(),
                assetEntry.getGroupId());
    }

    protected void postProcessFullQuery(BooleanQuery fullQuery, SearchContext searchContext) throws Exception {
    }

    protected void processHits(SearchContext searchContext, Hits hits) throws SearchException {

        HitsProcessorRegistryUtil.process(searchContext, hits);
    }

    protected void resetFullQuery(SearchContext searchContext) {
        searchContext.clearFullQueryEntryClassNames();

        for (RelatedEntryIndexer relatedEntryIndexer : RelatedEntryIndexerRegistryUtil.getRelatedEntryIndexers()) {

            relatedEntryIndexer.updateFullQuery(searchContext);
        }
    }

    protected void setDefaultSelectedFieldNames(String... defaultLocalizedFieldNames) {

        _defaultSelectedFieldNames = defaultLocalizedFieldNames;
    }

    protected void setDefaultSelectedLocalizedFieldNames(String... defaultLocalizedFieldNames) {

        _defaultSelectedLocalizedFieldNames = defaultLocalizedFieldNames;
    }

    protected void setFilterSearch(boolean filterSearch) {
        _filterSearch = filterSearch;
    }

    protected void setPermissionAware(boolean permissionAware) {
        _permissionAware = permissionAware;
    }

    protected void setStagingAware(boolean stagingAware) {
        _stagingAware = stagingAware;
    }

    private void _addPreFilters(BooleanFilter queryBooleanFilter, Map<String, Indexer<?>> entryClassNameIndexerMap,
            SearchContext searchContext) throws Exception {

        _preFilterContributor.contribute(queryBooleanFilter, entryClassNameIndexerMap, searchContext);
    }

    private void _addSearchTerms(BooleanQuery searchQuery, BooleanFilter fullQueryBooleanFilter,
            SearchContext searchContext) throws Exception {

        postProcessSearchQuery(searchQuery, fullQueryBooleanFilter, searchContext);

        for (IndexerPostProcessor indexerPostProcessor : _indexerPostProcessors) {

            indexerPostProcessor.postProcessSearchQuery(searchQuery, fullQueryBooleanFilter, searchContext);
        }
    }

    private Map<String, Indexer<?>> _getEntryClassNameIndexerMap(String[] entryClassNames, String searchEngineId) {

        Map<String, Indexer<?>> entryClassNameIndexerMap = new LinkedHashMap<>();

        for (String entryClassName : entryClassNames) {
            Indexer<?> indexer = IndexerRegistryUtil.getIndexer(entryClassName);

            if (indexer == null) {
                continue;
            }

            if (!searchEngineId.equals(indexer.getSearchEngineId())) {
                continue;
            }

            entryClassNameIndexerMap.put(entryClassName, indexer);
        }

        return entryClassNameIndexerMap;
    }

    private static final long _DEFAULT_FOLDER_ID = 0L;

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

    private static volatile ExpandoQueryContributor _expandoQueryContributor = ServiceProxyFactory
            .newServiceTrackedInstance(ExpandoQueryContributor.class, BaseIndexer.class, "_expandoQueryContributor",
                    false);
    private static volatile PreFilterContributor _preFilterContributor = ServiceProxyFactory
            .newServiceTrackedInstance(PreFilterContributor.class, BaseIndexer.class, "_preFilterContributor",
                    false);
    private static volatile SearchResultPermissionFilterFactory _searchResultPermissionFilterFactory = ServiceProxyFactory
            .newServiceTrackedInstance(SearchResultPermissionFilterFactory.class, BaseIndexer.class,
                    "_searchResultPermissionFilterFactory", false);

    private boolean _commitImmediately;
    private String[] _defaultSelectedFieldNames;
    private String[] _defaultSelectedLocalizedFieldNames;
    private final Document _document = new DocumentImpl();
    private List<DocumentContributor> _documentContributors;
    private boolean _filterSearch;
    private Boolean _indexerEnabled;
    private IndexerPostProcessor[] _indexerPostProcessors = new IndexerPostProcessor[0];
    private boolean _permissionAware;
    private String _searchEngineId;
    private boolean _selectAllLocales;
    private boolean _stagingAware = true;

}