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