/* * (C) Copyright 2010 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Anahide Tchertchian */ package org.nuxeo.ecm.platform.query.nxql; import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.IterableQueryResult; import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.ecm.core.api.SortInfo; import org.nuxeo.ecm.core.query.sql.NXQL; import org.nuxeo.ecm.platform.query.api.AbstractPageProvider; import org.nuxeo.ecm.platform.query.api.PageProviderDefinition; import org.nuxeo.ecm.platform.query.api.PageSelections; import org.nuxeo.ecm.platform.query.api.QuickFilter; import org.nuxeo.ecm.platform.query.api.WhereClauseDefinition; /** * Page provider performing a queryAndFetch on a core session. * <p> * It builds the query at each call so that it can refresh itself when the query changes. * <p> * <p> * The page provider property named {@link #CORE_SESSION_PROPERTY} is used to pass the {@link CoreSession} instance that * will perform the query. The optional property {@link #CHECK_QUERY_CACHE_PROPERTY} can be set to "true" to avoid * performing the query again if it did not change. * <p> * Since 6.0, the page provider property named {@link #LANGUAGE_PROPERTY} allows specifying the query language (NXQL, * NXTAG,...). * <p> * Also since 6.0, the page provider property named {@link #USE_UNRESTRICTED_SESSION_PROPERTY} allows specifying whether * the query should be run as unrestricted. * * @author Anahide Tchertchian * @since 5.4 */ public class CoreQueryAndFetchPageProvider extends AbstractPageProvider<Map<String, Serializable>> { public static final String CORE_SESSION_PROPERTY = "coreSession"; public static final String CHECK_QUERY_CACHE_PROPERTY = "checkQueryCache"; /** * Boolean property stating that query should be unrestricted. * * @since 6.0 */ public static final String USE_UNRESTRICTED_SESSION_PROPERTY = "useUnrestrictedSession"; /** * @since 6.0: alow specifying the query language (NXQL, NXTAG,...) */ public static final String LANGUAGE_PROPERTY = "language"; private static final long serialVersionUID = 1L; private static final Log log = LogFactory.getLog(CoreQueryDocumentPageProvider.class); protected String query; protected List<Map<String, Serializable>> currentItems; protected CoreSession getCoreSession() { CoreSession coreSession = null; Map<String, Serializable> props = getProperties(); coreSession = (CoreSession) props.get(CORE_SESSION_PROPERTY); return coreSession; } @Override public List<Map<String, Serializable>> getCurrentPage() { checkQueryCache(); CoreSession coreSession = null; long t0 = System.currentTimeMillis(); if (currentItems == null) { errorMessage = null; error = null; if (query == null) { buildQuery(); } if (query == null) { throw new NuxeoException(String.format("Cannot perform null query: check provider '%s'", getName())); } currentItems = new ArrayList<Map<String, Serializable>>(); Map<String, Serializable> props = getProperties(); coreSession = getCoreSession(); if (coreSession == null) { throw new NuxeoException("cannot find core session"); } IterableQueryResult result = null; try { long minMaxPageSize = getMinMaxPageSize(); long offset = getCurrentPageOffset(); if (log.isDebugEnabled()) { log.debug(String.format("Perform query for provider '%s': '%s' with pageSize=%s, offset=%s", getName(), query, Long.valueOf(minMaxPageSize), Long.valueOf(offset))); } final String language = getQueryLanguage(); final boolean useUnrestricted = useUnrestrictedSession(); if (useUnrestricted) { CoreQueryAndFetchUnrestrictedSessionRunner r = new CoreQueryAndFetchUnrestrictedSessionRunner( coreSession, query, language); r.runUnrestricted(); result = r.getResult(); } else { result = coreSession.queryAndFetch(query, language); } long resultsCount = result.size(); setResultsCount(resultsCount); if (offset < resultsCount) { result.skipTo(offset); } Iterator<Map<String, Serializable>> it = result.iterator(); int pos = 0; while (it.hasNext() && (maxPageSize == 0 || pos < minMaxPageSize)) { pos += 1; Map<String, Serializable> item = it.next(); currentItems.add(item); } if (log.isDebugEnabled()) { log.debug(String.format("Performed query for provider '%s': got %s hits", getName(), Long.valueOf(resultsCount))); } // refresh may have triggered display of an empty page => go // back to first page or forward to last page depending on // results count and page size long pageSize = getPageSize(); if (pageSize != 0) { if (offset != 0 && currentItems.size() == 0) { if (resultsCount == 0) { // fetch first page directly if (log.isDebugEnabled()) { log.debug(String.format( "Current page %s is not the first one but " + "shows no result and there are " + "no results => rewind to first page", Long.valueOf(getCurrentPageIndex()))); } firstPage(); } else { // fetch last page if (log.isDebugEnabled()) { log.debug(String.format( "Current page %s is not the first one but " + "shows no result and there are " + "%s results => fetch last page", Long.valueOf(getCurrentPageIndex()), Long.valueOf(resultsCount))); } lastPage(); } // fetch current page again getCurrentPage(); } } } catch (NuxeoException e) { errorMessage = e.getMessage(); error = e; log.warn(e.getMessage(), e); } finally { if (result != null) { result.close(); } } } if (coreSession == null) { coreSession = getCoreSession(); } // send event for statistics ! fireSearchEvent(coreSession.getPrincipal(), query, currentItems, System.currentTimeMillis() - t0); return currentItems; } protected void buildQuery() { List<SortInfo> sort = null; List<QuickFilter> quickFilters = getQuickFilters(); String quickFiltersClause = ""; if (quickFilters != null && !quickFilters.isEmpty()) { sort = new ArrayList<>(); for (QuickFilter quickFilter : quickFilters) { String clause = quickFilter.getClause(); if (!quickFiltersClause.isEmpty() && clause != null) { quickFiltersClause = NXQLQueryBuilder.appendClause(quickFiltersClause, clause); } else { quickFiltersClause = clause != null ? clause : ""; } sort.addAll(quickFilter.getSortInfos()); } } else if (sortInfos != null) { sort = sortInfos; } SortInfo[] sortArray = null; if (sort != null) { sortArray = sort.toArray(new SortInfo[] {}); } String newQuery; PageProviderDefinition def = getDefinition(); WhereClauseDefinition whereClause = def.getWhereClause(); if (whereClause == null) { String originalPattern = def.getPattern(); String pattern = quickFiltersClause.isEmpty() ? originalPattern : StringUtils.containsIgnoreCase(originalPattern, " WHERE ") ? NXQLQueryBuilder.appendClause(originalPattern, quickFiltersClause) : originalPattern + " WHERE " + quickFiltersClause; newQuery = NXQLQueryBuilder.getQuery(pattern, getParameters(), def.getQuotePatternParameters(), def.getEscapePatternParameters(), getSearchDocumentModel(), sortArray); } else { DocumentModel searchDocumentModel = getSearchDocumentModel(); if (searchDocumentModel == null) { throw new NuxeoException(String.format( "Cannot build query of provider '%s': " + "no search document model is set", getName())); } newQuery = NXQLQueryBuilder.getQuery(searchDocumentModel, whereClause, quickFiltersClause, getParameters(), sortArray); } if (query != null && newQuery != null && !newQuery.equals(query)) { // query has changed => refresh refresh(); } query = newQuery; } @Override public PageSelections<Map<String, Serializable>> getCurrentSelectPage() { checkQueryCache(); return super.getCurrentSelectPage(); } protected void checkQueryCache() { // maybe handle refresh of select page according to query if (getBooleanProperty(CHECK_QUERY_CACHE_PROPERTY, false)) { buildQuery(); } } protected boolean useUnrestrictedSession() { return getBooleanProperty(USE_UNRESTRICTED_SESSION_PROPERTY, false); } protected String getQueryLanguage() { Map<String, Serializable> props = getProperties(); if (props.containsKey(LANGUAGE_PROPERTY)) { return (String) props.get(LANGUAGE_PROPERTY); } return NXQL.NXQL; } public String getCurrentQuery() { return query; } @Override protected void pageChanged() { currentItems = null; super.pageChanged(); } @Override public void refresh() { query = null; currentItems = null; super.refresh(); } }