/*
* (C) Copyright 2010-2016 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.api;
import java.io.IOException;
import java.io.Serializable;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.NuxeoException;
import org.nuxeo.ecm.core.api.SortInfo;
import org.nuxeo.ecm.core.api.impl.SimpleDocumentModel;
import org.nuxeo.ecm.core.api.model.Property;
import org.nuxeo.ecm.core.event.EventContext;
import org.nuxeo.ecm.core.event.EventService;
import org.nuxeo.ecm.core.event.impl.UnboundEventContext;
import org.nuxeo.ecm.core.io.registry.MarshallerHelper;
import org.nuxeo.ecm.core.io.registry.context.RenderingContext;
import org.nuxeo.ecm.platform.query.nxql.CoreQueryAndFetchPageProvider;
import org.nuxeo.ecm.platform.query.nxql.CoreQueryDocumentPageProvider;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.services.config.ConfigurationService;
/**
* Basic implementation for a {@link PageProvider}.
* <p>
* Provides next/prev standard logics, and helper methods for retrieval of items and first/next/prev/last buttons
* display as well as other display information (number of pages for instance).
* <p>
* Also handles selection by providing a default implementation of {@link #getCurrentSelectPage()} working in
* conjunction with {@link #setSelectedEntries(List)}.
*
* @author Anahide Tchertchian
*/
public abstract class AbstractPageProvider<T> implements PageProvider<T> {
public static final Log log = LogFactory.getLog(AbstractPageProvider.class);
private static final long serialVersionUID = 1L;
/**
* property used to enable globally tracking : property should contains the list of pageproviders to be tracked
*
* @since 7.4
*/
public static final String PAGEPROVIDER_TRACK_PROPERTY_NAME = "nuxeo.pageprovider.track";
/**
* lists schemas prefixes that should be skipped when extracting "search fields" (tracking) from searchDocumentModel
*
* @since 7.4
*/
protected static final List<String> SKIPPED_SCHEMAS_FOR_SEARCHFIELD = Collections.singletonList("cvd");
protected String name;
protected long offset = 0;
protected long pageSize = 0;
protected List<Long> pageSizeOptions;
protected long maxPageSize = getDefaultMaxPageSize();
protected long resultsCount = UNKNOWN_SIZE;
protected int currentEntryIndex = 0;
/**
* Integer keeping track of the higher page index giving results. Useful for enabling or disabling the nextPage
* action when number of results cannot be known.
*
* @since 5.5
*/
protected int currentHigherNonEmptyPageIndex = 0;
protected List<SortInfo> sortInfos;
protected boolean sortable = false;
protected List<T> selectedEntries;
protected PageSelections<T> currentSelectPage;
protected Map<String, Serializable> properties;
protected Object[] parameters;
protected DocumentModel searchDocumentModel;
/**
* @since 8.4
*/
protected List<QuickFilter> quickFilters;
protected List<String> highlights;
protected String errorMessage;
protected Throwable error;
protected PageProviderDefinition definition;
protected PageProviderChangedListener pageProviderChangedListener;
/**
* Returns the list of current page items.
* <p>
* Custom implementation can be added here, based on the page provider properties, parameters and
* {@link WhereClauseDefinition} on the {@link PageProviderDefinition}, as well as search document, sort
* information, etc...
* <p>
* Implementation of this method usually consists in setting a non-null value to a field caching current items, and
* nullifying this field by overriding {@link #pageChanged()} and {@link #refresh()}.
* <p>
* Fields {@link #errorMessage} and {@link #error} can also be filled to provide accurate feedback in case an error
* occurs during the search.
* <p>
* When items are retrieved, a call to {@link #setResultsCount(long)} should be made to ensure proper pagination as
* implemented in this abstract class. The implementation in {@link CoreQueryAndFetchPageProvider} is a good example
* when the total results count is known.
* <p>
* If for performance reasons, for instance, the number of results cannot be known, a fall-back strategy can be
* applied to provide the "next" button but not the "last" one, by calling
* {@link #getCurrentHigherNonEmptyPageIndex()} and {@link #setCurrentHigherNonEmptyPageIndex(int)}. In this case,
* {@link CoreQueryDocumentPageProvider} is a good example.
*/
@Override
public abstract List<T> getCurrentPage();
/**
* Page change hook, to override for custom behavior
* <p>
* When overriding it, call {@code super.pageChanged()} as last statement to make sure that the
* {@link PageProviderChangedListener} is called with the up-to-date @{code PageProvider} state.
*/
protected void pageChanged() {
currentEntryIndex = 0;
currentSelectPage = null;
notifyPageChanged();
}
@Override
public void firstPage() {
long pageSize = getPageSize();
if (pageSize == 0) {
// do nothing
return;
}
long offset = getCurrentPageOffset();
if (offset != 0) {
setCurrentPageOffset(0);
pageChanged();
}
}
@Override
public long getCurrentPageIndex() {
long pageSize = getPageSize();
if (pageSize == 0) {
return 0;
}
long offset = getCurrentPageOffset();
return offset / pageSize;
}
@Override
public long getCurrentPageOffset() {
return offset;
}
@Override
public void setCurrentPageOffset(long offset) {
this.offset = offset;
}
@Override
public long getCurrentPageSize() {
List<T> currentItems = getCurrentPage();
if (currentItems != null) {
return currentItems.size();
}
return 0;
}
@Override
public String getName() {
return name;
}
@Override
public long getNumberOfPages() {
long pageSize = getPageSize();
// ensure 1 if no pagination
if (pageSize == 0) {
return 1;
}
// take max page size into into account
pageSize = getMinMaxPageSize();
if (pageSize == 0) {
return 1;
}
long resultsCount = getResultsCount();
if (resultsCount < 0) {
return 0;
} else {
return (1 + (resultsCount - 1) / pageSize);
}
}
@Override
public void setCurrentPageIndex(long currentPageIndex) {
long pageSize = getPageSize();
long offset = currentPageIndex * pageSize;
setCurrentPageOffset(offset);
pageChanged();
}
@Override
public List<T> setCurrentPage(long page) {
setCurrentPageIndex(page);
return getCurrentPage();
}
@Override
public long getPageSize() {
return pageSize;
}
@Override
public void setPageSize(long pageSize) {
long localPageSize = getPageSize();
if (localPageSize != pageSize) {
this.pageSize = pageSize;
// reset offset too
setCurrentPageOffset(0);
refresh();
}
}
@Override
public List<Long> getPageSizeOptions() {
List<Long> res = new ArrayList<>();
if (pageSizeOptions != null) {
res.addAll(pageSizeOptions);
}
// include the actual page size of page provider if not present
long ppsize = getPageSize();
if (ppsize > 0 && !res.contains(ppsize)) {
res.add(Long.valueOf(ppsize));
}
Collections.sort(res);
return res;
}
@Override
public void setPageSizeOptions(List<Long> options) {
pageSizeOptions = options;
}
@Override
public List<SortInfo> getSortInfos() {
// break reference
List<SortInfo> res = new ArrayList<>();
if (sortInfos != null) {
res.addAll(sortInfos);
}
return res;
}
@Override
public SortInfo getSortInfo() {
if (sortInfos != null && !sortInfos.isEmpty()) {
return sortInfos.get(0);
}
return null;
}
protected boolean sortInfoChanged(List<SortInfo> oldSortInfos, List<SortInfo> newSortInfos) {
if (oldSortInfos == null && newSortInfos == null) {
return false;
} else if (oldSortInfos == null) {
oldSortInfos = Collections.emptyList();
} else if (newSortInfos == null) {
newSortInfos = Collections.emptyList();
}
if (oldSortInfos.size() != newSortInfos.size()) {
return true;
}
for (int i = 0; i < oldSortInfos.size(); i++) {
SortInfo oldSort = oldSortInfos.get(i);
SortInfo newSort = newSortInfos.get(i);
if (oldSort == null && newSort == null) {
continue;
} else if (oldSort == null || newSort == null) {
return true;
}
if (!oldSort.equals(newSort)) {
return true;
}
}
return false;
}
@Override
public void setQuickFilters(List<QuickFilter> quickFilters) {
this.quickFilters = quickFilters;
}
@Override
public List<QuickFilter> getQuickFilters() {
return quickFilters;
}
@Override
public List<QuickFilter> getAvailableQuickFilters() {
return definition != null ? definition.getQuickFilters() : null;
}
@Override
public void addQuickFilter(QuickFilter quickFilter) {
if (quickFilters == null) {
quickFilters = new ArrayList<>();
}
quickFilters.add(quickFilter);
}
@Override
public void setSortInfos(List<SortInfo> sortInfo) {
if (sortInfoChanged(this.sortInfos, sortInfo)) {
this.sortInfos = sortInfo;
refresh();
}
}
@Override
public void setSortInfo(SortInfo sortInfo) {
List<SortInfo> newSortInfos = new ArrayList<>();
if (sortInfo != null) {
newSortInfos.add(sortInfo);
}
setSortInfos(newSortInfos);
}
@Override
public void setSortInfo(String sortColumn, boolean sortAscending, boolean removeOtherSortInfos) {
if (removeOtherSortInfos) {
SortInfo sortInfo = new SortInfo(sortColumn, sortAscending);
setSortInfo(sortInfo);
} else {
if (getSortInfoIndex(sortColumn, sortAscending) != -1) {
// do nothing: sort on this column is not set
} else if (getSortInfoIndex(sortColumn, !sortAscending) != -1) {
// change direction
List<SortInfo> newSortInfos = new ArrayList<>();
for (SortInfo sortInfo : getSortInfos()) {
if (sortColumn.equals(sortInfo.getSortColumn())) {
newSortInfos.add(new SortInfo(sortColumn, sortAscending));
} else {
newSortInfos.add(sortInfo);
}
}
setSortInfos(newSortInfos);
} else {
// just add it
addSortInfo(sortColumn, sortAscending);
}
}
}
@Override
public void addSortInfo(String sortColumn, boolean sortAscending) {
SortInfo sortInfo = new SortInfo(sortColumn, sortAscending);
List<SortInfo> sortInfos = getSortInfos();
if (sortInfos == null) {
setSortInfo(sortInfo);
} else {
sortInfos.add(sortInfo);
setSortInfos(sortInfos);
}
}
@Override
public int getSortInfoIndex(String sortColumn, boolean sortAscending) {
List<SortInfo> sortInfos = getSortInfos();
if (sortInfos == null || sortInfos.isEmpty()) {
return -1;
} else {
SortInfo sortInfo = new SortInfo(sortColumn, sortAscending);
return sortInfos.indexOf(sortInfo);
}
}
@Override
public List<String> getHighlights() {
return highlights;
}
@Override
public void setHighlights(List<String> highlights) {
this.highlights = highlights;
}
@Override
public boolean isNextPageAvailable() {
long pageSize = getPageSize();
if (pageSize == 0) {
return false;
}
long resultsCount = getResultsCount();
if (resultsCount < 0) {
long currentPageIndex = getCurrentPageIndex();
return currentPageIndex < getCurrentHigherNonEmptyPageIndex() + getMaxNumberOfEmptyPages();
} else {
long offset = getCurrentPageOffset();
return resultsCount > pageSize + offset;
}
}
@Override
public boolean isLastPageAvailable() {
long resultsCount = getResultsCount();
if (resultsCount < 0) {
return false;
}
return isNextPageAvailable();
}
@Override
public boolean isPreviousPageAvailable() {
long offset = getCurrentPageOffset();
return offset > 0;
}
@Override
public void lastPage() {
long pageSize = getPageSize();
long resultsCount = getResultsCount();
if (pageSize == 0 || resultsCount < 0) {
// do nothing
return;
}
if (resultsCount % pageSize == 0) {
setCurrentPageOffset(resultsCount - pageSize);
} else {
setCurrentPageOffset(resultsCount - resultsCount % pageSize);
}
pageChanged();
}
@Override
public void nextPage() {
long pageSize = getPageSize();
if (pageSize == 0) {
// do nothing
return;
}
long offset = getCurrentPageOffset();
offset += pageSize;
setCurrentPageOffset(offset);
pageChanged();
}
@Override
public void previousPage() {
long pageSize = getPageSize();
if (pageSize == 0) {
// do nothing
return;
}
long offset = getCurrentPageOffset();
if (offset >= pageSize) {
offset -= pageSize;
setCurrentPageOffset(offset);
pageChanged();
}
}
/**
* Refresh hook, to override for custom behavior
* <p>
* When overriding it, call {@code super.refresh()} as last statement to make sure that the
* {@link PageProviderChangedListener} is called with the up-to-date @{code PageProvider} state.
*/
@Override
public void refresh() {
setResultsCount(UNKNOWN_SIZE);
setCurrentHigherNonEmptyPageIndex(-1);
currentSelectPage = null;
errorMessage = null;
error = null;
notifyRefresh();
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String getCurrentPageStatus() {
long total = getNumberOfPages();
long current = getCurrentPageIndex() + 1;
if (total <= 0) {
// number of pages unknown or there is only one page
return String.format("%d", Long.valueOf(current));
} else {
return String.format("%d/%d", Long.valueOf(current), Long.valueOf(total));
}
}
@Override
public boolean isNextEntryAvailable() {
long pageSize = getPageSize();
long resultsCount = getResultsCount();
if (pageSize == 0) {
if (resultsCount < 0) {
// results count unknown
long currentPageSize = getCurrentPageSize();
return currentEntryIndex < currentPageSize - 1;
} else {
return currentEntryIndex < resultsCount - 1;
}
} else {
long currentPageSize = getCurrentPageSize();
if (currentEntryIndex < currentPageSize - 1) {
return true;
}
if (resultsCount < 0) {
// results count unknown => do not look for entry in next page
return false;
} else {
return isNextPageAvailable();
}
}
}
@Override
public boolean isPreviousEntryAvailable() {
return (currentEntryIndex != 0 || isPreviousPageAvailable());
}
@Override
public void nextEntry() {
long pageSize = getPageSize();
long resultsCount = getResultsCount();
if (pageSize == 0) {
if (resultsCount < 0) {
// results count unknown
long currentPageSize = getCurrentPageSize();
if (currentEntryIndex < currentPageSize - 1) {
currentEntryIndex++;
return;
}
} else {
if (currentEntryIndex < resultsCount - 1) {
currentEntryIndex++;
return;
}
}
} else {
long currentPageSize = getCurrentPageSize();
if (currentEntryIndex < currentPageSize - 1) {
currentEntryIndex++;
return;
}
if (resultsCount >= 0) {
// if results count is unknown, do not look for entry in next
// page
if (isNextPageAvailable()) {
nextPage();
currentEntryIndex = 0;
return;
}
}
}
}
@Override
public void previousEntry() {
if (currentEntryIndex > 0) {
currentEntryIndex--;
return;
}
if (!isPreviousPageAvailable()) {
return;
}
previousPage();
List<T> currentPage = getCurrentPage();
if (currentPage == null || currentPage.isEmpty()) {
// things may have changed since last query
currentEntryIndex = 0;
} else {
currentEntryIndex = (new Long(getPageSize() - 1)).intValue();
}
}
@Override
public T getCurrentEntry() {
List<T> currentPage = getCurrentPage();
if (currentPage == null || currentPage.isEmpty()) {
return null;
}
return currentPage.get(currentEntryIndex);
}
@Override
public void setCurrentEntry(T entry) {
List<T> currentPage = getCurrentPage();
if (currentPage == null || currentPage.isEmpty()) {
throw new NuxeoException(String.format("Entry '%s' not found in current page", entry));
}
int i = currentPage.indexOf(entry);
if (i == -1) {
throw new NuxeoException(String.format("Entry '%s' not found in current page", entry));
}
currentEntryIndex = i;
}
@Override
public void setCurrentEntryIndex(long index) {
int intIndex = new Long(index).intValue();
List<T> currentPage = getCurrentPage();
if (currentPage == null || currentPage.isEmpty()) {
throw new NuxeoException(String.format("Index %s not found in current page", new Integer(intIndex)));
}
if (index >= currentPage.size()) {
throw new NuxeoException(String.format("Index %s not found in current page", new Integer(intIndex)));
}
currentEntryIndex = intIndex;
}
@Override
public long getResultsCount() {
return resultsCount;
}
@Override
public Map<String, Serializable> getProperties() {
// break reference
return new HashMap<>(properties);
}
@Override
public void setProperties(Map<String, Serializable> properties) {
this.properties = properties;
}
/**
* @since 6.0
*/
protected boolean getBooleanProperty(String propName, boolean defaultValue) {
Map<String, Serializable> props = getProperties();
if (props.containsKey(propName)) {
Serializable prop = props.get(propName);
if (prop instanceof String) {
return Boolean.parseBoolean((String) prop);
} else {
return Boolean.TRUE.equals(prop);
}
}
return defaultValue;
}
@Override
public void setResultsCount(long resultsCount) {
this.resultsCount = resultsCount;
setCurrentHigherNonEmptyPageIndex(-1);
}
@Override
public void setSortable(boolean sortable) {
this.sortable = sortable;
}
@Override
public boolean isSortable() {
return sortable;
}
@Override
public PageSelections<T> getCurrentSelectPage() {
if (currentSelectPage == null) {
List<PageSelection<T>> entries = new ArrayList<>();
List<T> currentPage = getCurrentPage();
currentSelectPage = new PageSelections<>();
currentSelectPage.setName(name);
if (currentPage != null && !currentPage.isEmpty()) {
if (selectedEntries == null || selectedEntries.isEmpty()) {
// no selection at all
for (T entry : currentPage) {
entries.add(new PageSelection<>(entry, false));
}
} else {
boolean allSelected = true;
for (T entry : currentPage) {
Boolean selected = Boolean.valueOf(selectedEntries.contains(entry));
if (!Boolean.TRUE.equals(selected)) {
allSelected = false;
}
entries.add(new PageSelection<>(entry, selected.booleanValue()));
}
if (allSelected) {
currentSelectPage.setSelected(true);
}
}
}
currentSelectPage.setEntries(entries);
}
return currentSelectPage;
}
@Override
public void setSelectedEntries(List<T> entries) {
this.selectedEntries = entries;
// reset current select page so that it's rebuilt
currentSelectPage = null;
}
@Override
public Object[] getParameters() {
return parameters;
}
@Override
public void setParameters(Object[] parameters) {
this.parameters = parameters;
}
@Override
public DocumentModel getSearchDocumentModel() {
return searchDocumentModel;
}
protected boolean searchDocumentModelChanged(DocumentModel oldDoc, DocumentModel newDoc) {
if (oldDoc == null && newDoc == null) {
return false;
} else if (oldDoc == null || newDoc == null) {
return true;
}
// do not compare properties and assume it's changed
return true;
}
@Override
public void setSearchDocumentModel(DocumentModel searchDocumentModel) {
if (searchDocumentModelChanged(this.searchDocumentModel, searchDocumentModel)) {
refresh();
}
this.searchDocumentModel = searchDocumentModel;
}
@Override
public String getErrorMessage() {
return errorMessage;
}
@Override
public Throwable getError() {
return error;
}
@Override
public boolean hasError() {
return error != null;
}
@Override
public PageProviderDefinition getDefinition() {
return definition;
}
@Override
public void setDefinition(PageProviderDefinition providerDefinition) {
this.definition = providerDefinition;
}
@Override
public long getMaxPageSize() {
return maxPageSize;
}
@Override
public void setMaxPageSize(long maxPageSize) {
this.maxPageSize = maxPageSize;
}
/**
* Returns the minimal value for the max page size, taking the lower value between the requested page size and the
* maximum accepted page size.
*
* @since 5.4.2
*/
public long getMinMaxPageSize() {
long pageSize = getPageSize();
long maxPageSize = getMaxPageSize();
if (maxPageSize < 0) {
maxPageSize = getDefaultMaxPageSize();
}
if (pageSize <= 0) {
return maxPageSize;
}
if (maxPageSize > 0 && maxPageSize < pageSize) {
return maxPageSize;
}
return pageSize;
}
/**
* Returns an integer keeping track of the higher page index giving results. Useful for enabling or disabling the
* nextPage action when number of results cannot be known.
*
* @since 5.5
*/
public int getCurrentHigherNonEmptyPageIndex() {
return currentHigherNonEmptyPageIndex;
}
/**
* Returns the page limit. The n first page we know they exist.
*
* @since 5.8
*/
@Override
public long getPageLimit() {
return PAGE_LIMIT_UNKNOWN;
}
public void setCurrentHigherNonEmptyPageIndex(int higherFilledPageIndex) {
this.currentHigherNonEmptyPageIndex = higherFilledPageIndex;
}
/**
* Returns the maximum number of empty pages that can be fetched empty (defaults to 1). Can be useful for displaying
* pages of a provider without results count.
*
* @since 5.5
*/
public int getMaxNumberOfEmptyPages() {
return 1;
}
protected long getDefaultMaxPageSize() {
long res = DEFAULT_MAX_PAGE_SIZE;
if (Framework.isInitialized()) {
ConfigurationService cs = Framework.getService(ConfigurationService.class);
String maxPageSize = cs.getProperty(DEFAULT_MAX_PAGE_SIZE_RUNTIME_PROP);
if (!StringUtils.isBlank(maxPageSize)) {
try {
res = Long.parseLong(maxPageSize.trim());
} catch (NumberFormatException e) {
log.warn(String.format(
"Invalid max page size defined for property " + "\"%s\": %s (waiting for a long value)",
DEFAULT_MAX_PAGE_SIZE_RUNTIME_PROP, maxPageSize));
}
}
}
return res;
}
@Override
public void setPageProviderChangedListener(PageProviderChangedListener listener) {
pageProviderChangedListener = listener;
}
/**
* Call the registered {@code PageProviderChangedListener}, if any, to notify that the page provider current page
* has changed.
*
* @since 5.7
*/
protected void notifyPageChanged() {
if (pageProviderChangedListener != null) {
pageProviderChangedListener.pageChanged(this);
}
}
/**
* Call the registered {@code PageProviderChangedListener}, if any, to notify that the page provider has refreshed.
*
* @since 5.7
*/
protected void notifyRefresh() {
if (pageProviderChangedListener != null) {
pageProviderChangedListener.refreshed(this);
}
}
@Override
public boolean hasChangedParameters(Object[] parameters) {
return getParametersChanged(getParameters(), parameters);
}
protected boolean getParametersChanged(Object[] oldParams, Object[] newParams) {
if (oldParams == null && newParams == null) {
return true;
} else if (oldParams != null && newParams != null) {
if (oldParams.length != newParams.length) {
return true;
}
for (int i = 0; i < oldParams.length; i++) {
if (oldParams[i] == null && newParams[i] == null) {
continue;
} else if (newParams[i] instanceof String[] && oldParams[i] instanceof String[]
&& Arrays.equals((String[]) oldParams[i], (String[]) newParams[i])) {
continue;
} else if (oldParams[i] != null && !oldParams[i].equals(newParams[i])) {
return true;
} else if (newParams[i] != null && !newParams[i].equals(oldParams[i])) {
return true;
}
}
return false;
}
return true;
}
@Override
public List<AggregateDefinition> getAggregateDefinitions() {
return definition.getAggregates();
}
@Override
public Map<String, Aggregate<? extends Bucket>> getAggregates() {
throw new NotImplementedException();
}
@Override
public boolean hasAggregateSupport() {
return false;
}
protected Boolean tracking = null;
/**
* @since 7.4
*/
protected boolean isTrackingEnabled() {
if (tracking != null) {
return tracking;
}
if (getDefinition().isUsageTrackingEnabled()) {
tracking = true;
} else {
String trackedPageProviders = Framework.getProperty(PAGEPROVIDER_TRACK_PROPERTY_NAME, "");
if ("*".equals(trackedPageProviders)) {
tracking = true;
} else {
List<String> pps = Arrays.asList(trackedPageProviders.split(","));
if (pps.contains(getDefinition().getName())) {
tracking = true;
} else {
tracking = false;
}
}
}
return tracking;
}
/**
* Send a search event so that PageProvider calls can be tracked by Audit or other statistic gathering process
*
* @since 7.4
*/
protected void fireSearchEvent(Principal principal, String query, List<T> entries, Long executionTimeMs) {
if (!isTrackingEnabled()) {
return;
}
Map<String, Serializable> props = new HashMap<>();
props.put("pageProviderName", getDefinition().getName());
props.put("effectiveQuery", query);
props.put("searchPattern", getDefinition().getPattern());
props.put("queryParams", getDefinition().getQueryParameters());
props.put("params", getParameters());
WhereClauseDefinition wc = getDefinition().getWhereClause();
if (wc != null) {
props.put("whereClause_fixedPart", wc.getFixedPart());
props.put("whereClause_select", wc.getSelectStatement());
}
DocumentModel searchDocumentModel = getSearchDocumentModel();
if (searchDocumentModel != null && !(searchDocumentModel instanceof SimpleDocumentModel)) {
RenderingContext rCtx = RenderingContext.CtxBuilder.properties("*").get();
try {
// the SearchDocumentModel is not a Document bound to the repository
// - it may not survive the Event Stacking (ShallowDocumentModel)
// - it may take too much space in memory
// => let's use JSON
String searchDocumentModelAsJson = MarshallerHelper.objectToJson(DocumentModel.class,
searchDocumentModel, rCtx);
props.put("searchDocumentModelAsJson", searchDocumentModelAsJson);
} catch (IOException e) {
log.error("Unable to Marshall SearchDocumentModel as JSON", e);
}
ArrayList<String> searchFields = new ArrayList<>();
// searchFields collects the non- null fields inside the SearchDocumentModel
// some schemas are skipped because they contains ContentView related info
for (String schema : searchDocumentModel.getSchemas()) {
for (Property prop : searchDocumentModel.getPropertyObjects(schema)) {
if (prop.getValue() != null
&& !SKIPPED_SCHEMAS_FOR_SEARCHFIELD.contains(prop.getSchema().getNamespace().prefix)) {
if (prop.isList()) {
if (ArrayUtils.isNotEmpty(prop.getValue(Object[].class))) {
searchFields.add(prop.getXPath());
}
} else {
searchFields.add(prop.getXPath());
}
}
}
}
props.put("searchFields", searchFields);
}
if (entries != null) {
props.put("resultsCountInPage", entries.size());
}
props.put("resultsCount", getResultsCount());
props.put("pageSize", getPageSize());
props.put("pageIndex", getCurrentPageIndex());
props.put("principal", principal.getName());
if (executionTimeMs != null) {
props.put("executionTimeMs", executionTimeMs);
}
incorporateAggregates(props);
EventService es = Framework.getService(EventService.class);
EventContext ctx = new UnboundEventContext(principal, props);
es.fireEvent(ctx.newEvent("search"));
}
/**
* Default (dummy) implementation that should be overridden by PageProvider actually dealing with Aggregates
*
* @since 7.4
*/
protected void incorporateAggregates(Map<String, Serializable> eventProps) {
List<AggregateDefinition> ags = getDefinition().getAggregates();
if (ags != null) {
ArrayList<HashMap<String, Serializable>> aggregates = new ArrayList<>();
for (AggregateDefinition ag : ags) {
HashMap<String, Serializable> agData = new HashMap<>();
agData.put("type", ag.getType());
agData.put("id", ag.getId());
agData.put("field", ag.getDocumentField());
agData.putAll(ag.getProperties());
ArrayList<HashMap<String, Serializable>> rangesData = new ArrayList<>();
if (ag.getDateRanges() != null) {
for (AggregateRangeDateDefinition range : ag.getDateRanges()) {
HashMap<String, Serializable> rangeData = new HashMap<>();
rangeData.put("from", range.getFromAsString());
rangeData.put("to", range.getToAsString());
rangesData.add(rangeData);
}
for (AggregateRangeDefinition range : ag.getRanges()) {
HashMap<String, Serializable> rangeData = new HashMap<>();
rangeData.put("from-dbl", range.getFrom());
rangeData.put("to-dbl", range.getTo());
rangesData.add(rangeData);
}
}
agData.put("ranges", rangesData);
aggregates.add(agData);
}
eventProps.put("aggregates", aggregates);
}
}
}