001/* 002 * (C) Copyright 2010-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 * Anahide Tchertchian 018 */ 019package org.nuxeo.ecm.platform.query.nxql; 020 021import java.io.Serializable; 022import java.util.ArrayList; 023import java.util.Iterator; 024import java.util.List; 025import java.util.Map; 026 027import org.apache.commons.lang3.StringUtils; 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030import org.nuxeo.ecm.core.api.CoreSession; 031import org.nuxeo.ecm.core.api.DocumentModel; 032import org.nuxeo.ecm.core.api.IterableQueryResult; 033import org.nuxeo.ecm.core.api.NuxeoException; 034import org.nuxeo.ecm.core.api.SortInfo; 035import org.nuxeo.ecm.core.query.sql.NXQL; 036import org.nuxeo.ecm.platform.query.api.AbstractPageProvider; 037import org.nuxeo.ecm.platform.query.api.PageProviderDefinition; 038import org.nuxeo.ecm.platform.query.api.PageSelections; 039import org.nuxeo.ecm.platform.query.api.QuickFilter; 040import org.nuxeo.ecm.platform.query.api.WhereClauseDefinition; 041 042/** 043 * Page provider performing a queryAndFetch on a core session. 044 * <p> 045 * It builds the query at each call so that it can refresh itself when the query changes. 046 * <p> 047 * The page provider property named {@link #CORE_SESSION_PROPERTY} is used to pass the {@link CoreSession} instance that 048 * will perform the query. The optional property {@link #CHECK_QUERY_CACHE_PROPERTY} can be set to "true" to avoid 049 * performing the query again if it did not change. 050 * <p> 051 * Since 6.0, the page provider property named {@link #LANGUAGE_PROPERTY} allows specifying the query language (NXQL, 052 * NXTAG,...). 053 * <p> 054 * Also since 6.0, the page provider property named {@link #USE_UNRESTRICTED_SESSION_PROPERTY} allows specifying whether 055 * the query should be run as unrestricted. 056 * 057 * @author Anahide Tchertchian 058 * @since 5.4 059 */ 060public class CoreQueryAndFetchPageProvider extends AbstractPageProvider<Map<String, Serializable>> { 061 062 public static final String CORE_SESSION_PROPERTY = "coreSession"; 063 064 public static final String CHECK_QUERY_CACHE_PROPERTY = "checkQueryCache"; 065 066 /** 067 * Boolean property stating that query should be unrestricted. 068 * 069 * @since 6.0 070 */ 071 public static final String USE_UNRESTRICTED_SESSION_PROPERTY = "useUnrestrictedSession"; 072 073 /** 074 * @since 6.0: alow specifying the query language (NXQL, NXTAG,...) 075 */ 076 public static final String LANGUAGE_PROPERTY = "language"; 077 078 private static final long serialVersionUID = 1L; 079 080 private static final Log log = LogFactory.getLog(CoreQueryDocumentPageProvider.class); 081 082 protected String query; 083 084 protected List<Map<String, Serializable>> currentItems; 085 086 protected CoreSession getCoreSession() { 087 CoreSession coreSession; 088 Map<String, Serializable> props = getProperties(); 089 coreSession = (CoreSession) props.get(CORE_SESSION_PROPERTY); 090 return coreSession; 091 } 092 093 @Override 094 public List<Map<String, Serializable>> getCurrentPage() { 095 checkQueryCache(); 096 CoreSession coreSession = null; 097 long t0 = System.currentTimeMillis(); 098 if (currentItems == null) { 099 errorMessage = null; 100 error = null; 101 102 if (query == null) { 103 buildQuery(); 104 } 105 if (query == null) { 106 throw new NuxeoException(String.format("Cannot perform null query: check provider '%s'", getName())); 107 } 108 109 currentItems = new ArrayList<>(); 110 111 coreSession = getCoreSession(); 112 if (coreSession == null) { 113 throw new NuxeoException("cannot find core session"); 114 } 115 116 IterableQueryResult result = null; 117 try { 118 119 long minMaxPageSize = getMinMaxPageSize(); 120 121 long offset = getCurrentPageOffset(); 122 if (log.isDebugEnabled()) { 123 log.debug(String.format("Perform query for provider '%s': '%s' with pageSize=%s, offset=%s", 124 getName(), query, Long.valueOf(minMaxPageSize), Long.valueOf(offset))); 125 } 126 127 final String language = getQueryLanguage(); 128 final boolean useUnrestricted = useUnrestrictedSession(); 129 if (useUnrestricted) { 130 CoreQueryAndFetchUnrestrictedSessionRunner r = new CoreQueryAndFetchUnrestrictedSessionRunner( 131 coreSession, query, language); 132 r.runUnrestricted(); 133 result = r.getResult(); 134 } else { 135 result = coreSession.queryAndFetch(query, language); 136 } 137 long resultsCount = result.size(); 138 setResultsCount(resultsCount); 139 if (offset < resultsCount) { 140 result.skipTo(offset); 141 } 142 143 Iterator<Map<String, Serializable>> it = result.iterator(); 144 int pos = 0; 145 while (it.hasNext() && (maxPageSize == 0 || pos < minMaxPageSize)) { 146 pos += 1; 147 Map<String, Serializable> item = it.next(); 148 currentItems.add(item); 149 } 150 151 if (log.isDebugEnabled()) { 152 log.debug(String.format("Performed query for provider '%s': got %s hits", getName(), 153 Long.valueOf(resultsCount))); 154 } 155 156 } catch (NuxeoException e) { 157 errorMessage = e.getMessage(); 158 error = e; 159 log.warn(e.getMessage(), e); 160 } finally { 161 if (result != null) { 162 result.close(); 163 } 164 } 165 } 166 167 if (coreSession == null) { 168 coreSession = getCoreSession(); 169 } 170 171 // send event for statistics ! 172 fireSearchEvent(coreSession.getPrincipal(), query, currentItems, System.currentTimeMillis() - t0); 173 174 return currentItems; 175 } 176 177 protected void buildQuery() { 178 List<SortInfo> sort = null; 179 List<QuickFilter> quickFilters = getQuickFilters(); 180 String quickFiltersClause = ""; 181 182 if (quickFilters != null && !quickFilters.isEmpty()) { 183 sort = new ArrayList<>(); 184 for (QuickFilter quickFilter : quickFilters) { 185 String clause = quickFilter.getClause(); 186 if (!quickFiltersClause.isEmpty() && clause != null) { 187 quickFiltersClause = NXQLQueryBuilder.appendClause(quickFiltersClause, clause); 188 } else { 189 quickFiltersClause = clause != null ? clause : ""; 190 } 191 sort.addAll(quickFilter.getSortInfos()); 192 } 193 } else if (sortInfos != null) { 194 sort = sortInfos; 195 } 196 197 SortInfo[] sortArray = null; 198 if (sort != null) { 199 sortArray = sort.toArray(new SortInfo[] {}); 200 } 201 202 String newQuery; 203 PageProviderDefinition def = getDefinition(); 204 WhereClauseDefinition whereClause = def.getWhereClause(); 205 if (whereClause == null) { 206 207 String originalPattern = def.getPattern(); 208 String pattern = quickFiltersClause.isEmpty() ? originalPattern 209 : StringUtils.containsIgnoreCase(originalPattern, " WHERE ") 210 ? NXQLQueryBuilder.appendClause(originalPattern, quickFiltersClause) 211 : originalPattern + " WHERE " + quickFiltersClause; 212 213 newQuery = NXQLQueryBuilder.getQuery(pattern, getParameters(), def.getQuotePatternParameters(), 214 def.getEscapePatternParameters(), getSearchDocumentModel(), sortArray); 215 } else { 216 217 DocumentModel searchDocumentModel = getSearchDocumentModel(); 218 if (searchDocumentModel == null) { 219 throw new NuxeoException(String.format( 220 "Cannot build query of provider '%s': " + "no search document model is set", getName())); 221 } 222 newQuery = NXQLQueryBuilder.getQuery(searchDocumentModel, whereClause, quickFiltersClause, getParameters(), 223 sortArray); 224 } 225 226 if (query != null && newQuery != null && !newQuery.equals(query)) { 227 // query has changed => refresh 228 refresh(); 229 } 230 query = newQuery; 231 } 232 233 @Override 234 public PageSelections<Map<String, Serializable>> getCurrentSelectPage() { 235 checkQueryCache(); 236 // fetch last page if current page index is beyond the last page or if there are no results to display 237 rewindSelectablePage(); 238 return super.getCurrentSelectPage(); 239 } 240 241 /** 242 * Fetch a page that can be selected. It loads the last page if we're targeting a page beyond the last one or 243 * the first page if there are no results to show and we're targeting anything other than the first page. 244 * 245 * Fix for NXP-8564. 246 */ 247 protected void rewindSelectablePage() { 248 long pageSize = getPageSize(); 249 if (pageSize != 0) { 250 if (offset != 0 && currentItems != null && currentItems.size() == 0) { 251 if (resultsCount == 0) { 252 // fetch first page directly 253 if (log.isDebugEnabled()) { 254 log.debug(String.format( 255 "Current page %s is not the first one but " + "shows no result and there are " 256 + "no results => rewind to first page", 257 Long.valueOf(getCurrentPageIndex()))); 258 } 259 firstPage(); 260 } else { 261 // fetch last page 262 if (log.isDebugEnabled()) { 263 log.debug(String.format( 264 "Current page %s is not the first one but " + "shows no result and there are " 265 + "%s results => fetch last page", 266 Long.valueOf(getCurrentPageIndex()), Long.valueOf(resultsCount))); 267 } 268 lastPage(); 269 } 270 // fetch current page again 271 getCurrentPage(); 272 } 273 } 274 } 275 276 protected void checkQueryCache() { 277 // maybe handle refresh of select page according to query 278 if (getBooleanProperty(CHECK_QUERY_CACHE_PROPERTY, false)) { 279 buildQuery(); 280 } 281 } 282 283 protected boolean useUnrestrictedSession() { 284 return getBooleanProperty(USE_UNRESTRICTED_SESSION_PROPERTY, false); 285 } 286 287 protected String getQueryLanguage() { 288 Map<String, Serializable> props = getProperties(); 289 if (props.containsKey(LANGUAGE_PROPERTY)) { 290 return (String) props.get(LANGUAGE_PROPERTY); 291 } 292 return NXQL.NXQL; 293 } 294 295 public String getCurrentQuery() { 296 return query; 297 } 298 299 @Override 300 protected void pageChanged() { 301 currentItems = null; 302 super.pageChanged(); 303 } 304 305 @Override 306 public void refresh() { 307 query = null; 308 currentItems = null; 309 super.refresh(); 310 } 311 312}