/* * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * bdelbosc */ package org.nuxeo.elasticsearch.provider; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.elasticsearch.index.query.QueryBuilder; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentModelList; import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.ecm.core.query.QueryParseException; import org.nuxeo.ecm.platform.query.api.Aggregate; import org.nuxeo.ecm.platform.query.api.AggregateDefinition; import org.nuxeo.ecm.platform.query.api.Bucket; import org.nuxeo.ecm.platform.query.nxql.CoreQueryDocumentPageProvider; import org.nuxeo.elasticsearch.aggregate.AggregateEsBase; import org.nuxeo.elasticsearch.aggregate.AggregateFactory; import org.nuxeo.elasticsearch.api.ElasticSearchService; import org.nuxeo.elasticsearch.api.EsResult; import org.nuxeo.elasticsearch.query.NxQueryBuilder; import org.nuxeo.elasticsearch.query.NxqlQueryConverter; import org.nuxeo.runtime.api.Framework; /** * Elasticsearch Page provider that converts the NXQL query build by CoreQueryDocumentPageProvider. * * @since 5.9.3 */ public class ElasticSearchNxqlPageProvider extends CoreQueryDocumentPageProvider { public static final String CORE_SESSION_PROPERTY = "coreSession"; public static final String SEARCH_ON_ALL_REPOSITORIES_PROPERTY = "searchAllRepositories"; protected static final Log log = LogFactory.getLog(ElasticSearchNxqlPageProvider.class); private static final long serialVersionUID = 1L; protected List<DocumentModel> currentPageDocuments; protected HashMap<String, Aggregate<? extends Bucket>> currentAggregates; @Override public List<DocumentModel> getCurrentPage() { long t0 = System.currentTimeMillis(); // use a cache if (currentPageDocuments != null) { return currentPageDocuments; } error = null; errorMessage = null; if (log.isDebugEnabled()) { log.debug(String.format("Perform query for provider '%s': with pageSize=%d, offset=%d", getName(), getMinMaxPageSize(), getCurrentPageOffset())); } currentPageDocuments = new ArrayList<>(); CoreSession coreSession = getCoreSession(); if (query == null) { buildQuery(coreSession); } if (query == null) { throw new NuxeoException(String.format("Cannot perform null query: check provider '%s'", getName())); } // Build and execute the ES query ElasticSearchService ess = Framework.getLocalService(ElasticSearchService.class); try { NxQueryBuilder nxQuery = new NxQueryBuilder(getCoreSession()).nxql(query) .offset((int) getCurrentPageOffset()) .limit(getLimit()) .addAggregates(buildAggregates()); if (searchOnAllRepositories()) { nxQuery.searchOnAllRepositories(); } List<String> highlightFields = getHighlights(); if (highlightFields != null && !highlightFields.isEmpty()) { nxQuery.highlight(highlightFields); } EsResult ret = ess.queryAndAggregate(nxQuery); DocumentModelList dmList = ret.getDocuments(); currentAggregates = new HashMap<>(ret.getAggregates().size()); for (Aggregate<Bucket> agg : ret.getAggregates()) { currentAggregates.put(agg.getId(), agg); } setResultsCount(dmList.totalSize()); currentPageDocuments = dmList; } catch (QueryParseException e) { error = e; errorMessage = e.getMessage(); log.warn(e.getMessage(), e); } // send event for statistics ! fireSearchEvent(getCoreSession().getPrincipal(), query, currentPageDocuments, System.currentTimeMillis() - t0); return currentPageDocuments; } protected int getLimit() { int ret = (int) getMinMaxPageSize(); if (ret == 0) { ret = -1; } return ret; } public QueryBuilder getCurrentQueryAsEsBuilder() { String nxql = getCurrentQuery(); return NxqlQueryConverter.toESQueryBuilder(nxql); } @Override protected void pageChanged() { currentPageDocuments = null; currentAggregates = null; super.pageChanged(); } @Override public void refresh() { currentPageDocuments = null; currentAggregates = null; super.refresh(); } @Override protected CoreSession getCoreSession() { Map<String, Serializable> props = getProperties(); CoreSession coreSession = (CoreSession) props.get(CORE_SESSION_PROPERTY); if (coreSession == null) { throw new NuxeoException("cannot find core session"); } return coreSession; } private List<AggregateEsBase<? extends Bucket>> buildAggregates() { ArrayList<AggregateEsBase<? extends Bucket>> ret = new ArrayList<>(getAggregateDefinitions().size()); for (AggregateDefinition def : getAggregateDefinitions()) { ret.add(AggregateFactory.create(def, getSearchDocumentModel())); } return ret; } protected boolean searchOnAllRepositories() { String value = (String) getProperties().get(SEARCH_ON_ALL_REPOSITORIES_PROPERTY); if (value == null) { return false; } return Boolean.parseBoolean(value); } @Override public boolean hasAggregateSupport() { return true; } @Override public Map<String, Aggregate<? extends Bucket>> getAggregates() { getCurrentPage(); return currentAggregates; } /** * Extends the default implementation to add results of aggregates * * @since 7.4 */ @Override protected void incorporateAggregates(Map<String, Serializable> eventProps) { super.incorporateAggregates(eventProps); if (currentAggregates != null) { HashMap<String, Serializable> aggregateMatches = new HashMap<>(); for (String key : currentAggregates.keySet()) { Aggregate<? extends Bucket> ag = currentAggregates.get(key); ArrayList<HashMap<String, Serializable>> buckets = new ArrayList<>(); for (Bucket bucket : ag.getBuckets()) { HashMap<String, Serializable> b = new HashMap<>(); b.put("key", bucket.getKey()); b.put("count", bucket.getDocCount()); buckets.add(b); } aggregateMatches.put(key, buckets); } eventProps.put("aggregatesMatches", aggregateMatches); } } }