001/* 002 * (C) Copyright 2014-2018 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * bdelbosc 018 */ 019 020package org.nuxeo.elasticsearch.provider; 021 022import java.io.Serializable; 023import java.util.ArrayList; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027 028import org.apache.commons.lang3.StringUtils; 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.elasticsearch.index.query.QueryBuilder; 032import org.elasticsearch.search.aggregations.Aggregation; 033import org.nuxeo.ecm.core.api.CoreSession; 034import org.nuxeo.ecm.core.api.DocumentModel; 035import org.nuxeo.ecm.core.api.DocumentModelList; 036import org.nuxeo.ecm.core.api.NuxeoException; 037import org.nuxeo.ecm.core.api.SortInfo; 038import org.nuxeo.ecm.core.query.QueryParseException; 039import org.nuxeo.ecm.platform.query.api.AbstractPageProvider; 040import org.nuxeo.ecm.platform.query.api.Aggregate; 041import org.nuxeo.ecm.platform.query.api.AggregateDefinition; 042import org.nuxeo.ecm.platform.query.api.Bucket; 043import org.nuxeo.ecm.platform.query.api.PageProviderDefinition; 044import org.nuxeo.ecm.platform.query.api.QuickFilter; 045import org.nuxeo.ecm.platform.query.api.WhereClauseDefinition; 046import org.nuxeo.ecm.platform.query.nxql.NXQLQueryBuilder; 047import org.nuxeo.elasticsearch.aggregate.AggregateEsBase; 048import org.nuxeo.elasticsearch.aggregate.AggregateFactory; 049import org.nuxeo.elasticsearch.api.ElasticSearchService; 050import org.nuxeo.elasticsearch.api.EsResult; 051import org.nuxeo.elasticsearch.query.NxQueryBuilder; 052import org.nuxeo.elasticsearch.query.PageProviderQueryBuilder; 053import org.nuxeo.runtime.api.Framework; 054 055public class ElasticSearchNativePageProvider extends AbstractPageProvider<DocumentModel> { 056 057 public static final String CORE_SESSION_PROPERTY = "coreSession"; 058 059 public static final String SEARCH_ON_ALL_REPOSITORIES_PROPERTY = "searchAllRepositories"; 060 061 protected static final Log log = LogFactory.getLog(ElasticSearchNativePageProvider.class); 062 063 private static final long serialVersionUID = 1L; 064 065 protected List<DocumentModel> currentPageDocuments; 066 067 protected Map<String, Aggregate<? extends Bucket>> currentAggregates; 068 069 @Override 070 public Map<String, Aggregate<? extends Bucket>> getAggregates() { 071 getCurrentPage(); 072 return currentAggregates; 073 } 074 075 @Override 076 public List<DocumentModel> getCurrentPage() { 077 long t0 = System.currentTimeMillis(); 078 079 // use a cache 080 if (currentPageDocuments != null) { 081 return currentPageDocuments; 082 } 083 error = null; 084 errorMessage = null; 085 if (log.isDebugEnabled()) { 086 log.debug(String.format("Perform query for provider '%s': with pageSize=%d, offset=%d", getName(), 087 getMinMaxPageSize(), getCurrentPageOffset())); 088 } 089 currentPageDocuments = new ArrayList<>(); 090 // Build the ES query 091 QueryBuilder query = makeQueryBuilder(); 092 SortInfo[] sortArray = null; 093 if (sortInfos != null) { 094 sortArray = sortInfos.toArray(new SortInfo[sortInfos.size()]); 095 } 096 // Execute the ES query 097 ElasticSearchService ess = Framework.getService(ElasticSearchService.class); 098 try { 099 NxQueryBuilder nxQuery = new NxQueryBuilder(getCoreSession()).esQuery(query) 100 .offset((int) getCurrentPageOffset()) 101 .limit((int) getMinMaxPageSize()) 102 .addSort(sortArray) 103 .addAggregates(buildAggregates()); 104 if (searchOnAllRepositories()) { 105 nxQuery.searchOnAllRepositories(); 106 } 107 108 List<String> highlightFields = getHighlights(); 109 if (highlightFields != null && !highlightFields.isEmpty()) { 110 nxQuery.highlight(highlightFields); 111 } 112 113 EsResult ret = ess.queryAndAggregate(nxQuery); 114 DocumentModelList dmList = ret.getDocuments(); 115 currentAggregates = new HashMap<>(ret.getAggregates().size()); 116 for (Aggregate<Bucket> agg : ret.getAggregates()) { 117 currentAggregates.put(agg.getId(), agg); 118 } 119 setResultsCount(dmList.totalSize()); 120 currentPageDocuments = dmList; 121 } catch (QueryParseException e) { 122 error = e; 123 errorMessage = e.getMessage(); 124 log.warn(e.getMessage(), e); 125 } 126 127 // send event for statistics ! 128 fireSearchEvent(getCoreSession().getPrincipal(), query.toString(), currentPageDocuments, 129 System.currentTimeMillis() - t0); 130 131 return currentPageDocuments; 132 } 133 134 private List<AggregateEsBase<? extends Aggregation, ? extends Bucket>> buildAggregates() { 135 ArrayList<AggregateEsBase<? extends Aggregation, ? extends Bucket>> ret = new ArrayList<>( 136 getAggregateDefinitions().size()); 137 boolean skip = isSkipAggregates(); 138 for (AggregateDefinition def : getAggregateDefinitions()) { 139 AggregateEsBase<? extends Aggregation, ? extends Bucket> agg = AggregateFactory.create(def, 140 getSearchDocumentModel()); 141 if (!skip || !agg.getSelection().isEmpty()) { 142 // if we want to skip aggregates but one is selected, it has to be computed to filter the result set 143 ret.add(AggregateFactory.create(def, getSearchDocumentModel())); 144 } 145 } 146 return ret; 147 } 148 149 @Override 150 public boolean hasAggregateSupport() { 151 return true; 152 } 153 154 protected QueryBuilder makeQueryBuilder() { 155 QueryBuilder ret; 156 PageProviderDefinition def = getDefinition(); 157 List<QuickFilter> quickFilters = getQuickFilters(); 158 String quickFiltersClause = ""; 159 160 if (quickFilters != null && !quickFilters.isEmpty()) { 161 for (QuickFilter quickFilter : quickFilters) { 162 String clause = quickFilter.getClause(); 163 if (!quickFiltersClause.isEmpty() && clause != null) { 164 quickFiltersClause = NXQLQueryBuilder.appendClause(quickFiltersClause, clause); 165 } else { 166 quickFiltersClause = clause != null ? clause : ""; 167 } 168 } 169 } 170 171 WhereClauseDefinition whereClause = def.getWhereClause(); 172 if (whereClause == null) { 173 174 String originalPattern = def.getPattern(); 175 String pattern = quickFiltersClause.isEmpty() ? originalPattern 176 : StringUtils.containsIgnoreCase(originalPattern, " WHERE ") 177 ? NXQLQueryBuilder.appendClause(originalPattern, quickFiltersClause) 178 : originalPattern + " WHERE " + quickFiltersClause; 179 180 ret = PageProviderQueryBuilder.makeQuery(pattern, getParameters(), def.getQuotePatternParameters(), 181 def.getEscapePatternParameters(), isNativeQuery()); 182 } else { 183 184 DocumentModel searchDocumentModel = getSearchDocumentModel(); 185 if (searchDocumentModel == null) { 186 throw new NuxeoException(String.format( 187 "Cannot build query of provider '%s': " + "no search document model is set", getName())); 188 } 189 ret = PageProviderQueryBuilder.makeQuery(searchDocumentModel, whereClause, quickFiltersClause, 190 getParameters(), isNativeQuery()); 191 } 192 return ret; 193 } 194 195 @Override 196 protected void pageChanged() { 197 currentPageDocuments = null; 198 currentAggregates = null; 199 super.pageChanged(); 200 } 201 202 @Override 203 public void refresh() { 204 currentPageDocuments = null; 205 currentAggregates = null; 206 super.refresh(); 207 } 208 209 protected CoreSession getCoreSession() { 210 Map<String, Serializable> props = getProperties(); 211 CoreSession coreSession = (CoreSession) props.get(CORE_SESSION_PROPERTY); 212 if (coreSession == null) { 213 throw new NuxeoException("cannot find core session"); 214 } 215 return coreSession; 216 } 217 218 protected boolean searchOnAllRepositories() { 219 String value = (String) getProperties().get(SEARCH_ON_ALL_REPOSITORIES_PROPERTY); 220 if (value == null) { 221 return false; 222 } 223 return Boolean.parseBoolean(value); 224 } 225 226 public boolean isNativeQuery() { 227 return true; 228 } 229}