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