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