001/*
002 * (C) Copyright 2010-2016 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.contentview.jsf;
020
021import static org.apache.commons.lang.StringUtils.isBlank;
022
023import java.util.ArrayList;
024import java.util.List;
025import java.util.Map;
026
027import javax.faces.context.ExternalContext;
028import javax.faces.context.FacesContext;
029
030import org.apache.commons.lang.StringUtils;
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033import org.jboss.seam.core.Events;
034import org.nuxeo.ecm.core.api.DocumentModel;
035import org.nuxeo.ecm.core.api.DocumentModelFactory;
036import org.nuxeo.ecm.core.api.PropertyException;
037import org.nuxeo.ecm.core.api.SortInfo;
038import org.nuxeo.ecm.core.api.model.impl.MapProperty;
039import org.nuxeo.ecm.platform.query.api.Aggregate;
040import org.nuxeo.ecm.platform.query.api.PageProvider;
041import org.nuxeo.ecm.platform.query.api.PageProviderChangedListener;
042import org.nuxeo.ecm.platform.query.api.QuickFilter;
043import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils;
044import org.nuxeo.runtime.api.Framework;
045
046import com.google.common.base.Function;
047
048/**
049 * Default implementation for the content view object.
050 * <p>
051 * Provides simple getters for attributes defined in the XMap descriptor, except cache key which is computed from
052 * currrent {@link FacesContext} instance if cache key is an EL expression.
053 * <p>
054 * The page provider is initialized calling {@link ContentViewService#getPageProvider}.
055 *
056 * @author Anahide Tchertchian
057 * @since 5.4
058 */
059public class ContentViewImpl implements ContentView, PageProviderChangedListener {
060
061    private static final long serialVersionUID = 1L;
062
063    private static final Log log = LogFactory.getLog(ContentViewImpl.class);
064
065    protected String name;
066
067    protected PageProvider<?> pageProvider;
068
069    protected String title;
070
071    protected boolean translateTitle;
072
073    protected String emptySentence;
074
075    protected boolean translateEmptySentence;
076
077    protected String iconPath;
078
079    protected boolean showTitle;
080
081    protected String selectionList;
082
083    protected String pagination;
084
085    protected List<String> actionCategories;
086
087    protected ContentViewLayout searchLayout;
088
089    protected List<ContentViewLayout> resultLayouts;
090
091    protected List<String> flags;
092
093    protected boolean currentResultLayoutSet = false;
094
095    protected ContentViewLayout currentResultLayout;
096
097    protected List<String> currentResultLayoutColumns;
098
099    protected String cacheKey;
100
101    protected Integer cacheSize;
102
103    protected List<String> refreshEventNames;
104
105    protected List<String> resetEventNames;
106
107    protected boolean useGlobalPageSize;
108
109    protected boolean showPageSizeSelector;
110
111    protected boolean showRefreshCommand;
112
113    protected boolean showFilterForm;
114
115    protected Long currentPageSize;
116
117    protected String[] queryParameters;
118
119    protected DocumentModel searchDocumentModel;
120
121    protected String searchDocumentModelBinding;
122
123    protected String searchDocumentModelType;
124
125    protected String resultColumnsBinding;
126
127    protected String resultLayoutBinding;
128
129    protected String pageSizeBinding;
130
131    protected String sortInfosBinding;
132
133    protected boolean waitForExecution = false;
134
135    protected String waitForExecutionSentence;
136
137    protected boolean executed = false;
138
139    /**
140     * @since 8.4
141     */
142    protected List<QuickFilter> quickFilters;
143
144    public ContentViewImpl(String name, String title, boolean translateTitle, String iconPath, String selectionList,
145            String pagination, List<String> actionCategories, ContentViewLayout searchLayout,
146            List<ContentViewLayout> resultLayouts, List<String> flags, String cacheKey, Integer cacheSize,
147            List<String> refreshEventNames, List<String> resetEventNames, boolean useGlobalPageSize,
148            String[] queryParameters, String searchDocumentModelBinding, String searchDocumentModelType,
149            String resultColumnsBinding, String resultLayoutBinding, String sortInfosBinding, String pageSizeBinding,
150            boolean showTitle, boolean showPageSizeSelector, boolean showRefreshCommand, boolean showFilterForm,
151            String emptySentence, boolean translateEmptySentence) {
152        this.name = name;
153        this.title = title;
154        this.translateTitle = translateTitle;
155        this.iconPath = iconPath;
156        this.selectionList = selectionList;
157        this.pagination = pagination;
158        this.actionCategories = actionCategories;
159        this.searchLayout = searchLayout;
160        this.resultLayouts = resultLayouts;
161        this.flags = flags;
162        this.cacheKey = cacheKey;
163        this.cacheSize = cacheSize;
164        if (cacheSize != null && cacheSize.intValue() <= 0) {
165            // force a static cache key
166            this.cacheKey = "static_key_no_cache";
167        }
168        this.refreshEventNames = refreshEventNames;
169        this.resetEventNames = resetEventNames;
170        this.useGlobalPageSize = useGlobalPageSize;
171        this.queryParameters = queryParameters;
172        this.searchDocumentModelBinding = searchDocumentModelBinding;
173        this.searchDocumentModelType = searchDocumentModelType;
174        this.resultColumnsBinding = resultColumnsBinding;
175        this.resultLayoutBinding = resultLayoutBinding;
176        this.pageSizeBinding = pageSizeBinding;
177        this.sortInfosBinding = sortInfosBinding;
178        this.showTitle = showTitle;
179        this.showPageSizeSelector = showPageSizeSelector;
180        this.showRefreshCommand = showRefreshCommand;
181        this.showFilterForm = showFilterForm;
182        this.emptySentence = emptySentence;
183        this.translateEmptySentence = translateEmptySentence;
184    }
185
186    @Override
187    public String getName() {
188        return name;
189    }
190
191    @Override
192    public String getTitle() {
193        return title;
194    }
195
196    @Override
197    public boolean getTranslateTitle() {
198        return translateTitle;
199    }
200
201    @Override
202    public String getIconPath() {
203        return iconPath;
204    }
205
206    @Override
207    public String getSelectionListName() {
208        return selectionList;
209    }
210
211    @Override
212    public String getPagination() {
213        return pagination;
214    }
215
216    @Override
217    public List<String> getActionsCategories() {
218        return actionCategories;
219    }
220
221    @Override
222    public ContentViewLayout getSearchLayout() {
223        return searchLayout;
224    }
225
226    @Override
227    public List<ContentViewLayout> getResultLayouts() {
228        return resultLayouts;
229    }
230
231    @Override
232    public ContentViewLayout getCurrentResultLayout() {
233        // resolve binding if it is set
234        if (!currentResultLayoutSet && !StringUtils.isBlank(resultLayoutBinding)) {
235            Object res = resolveWithSearchDocument(
236                    ctx -> ComponentTagUtils.resolveElExpression(ctx, resultLayoutBinding));
237            if (res != null && res instanceof String) {
238                setCurrentResultLayout((String) res);
239                currentResultLayoutSet = true;
240            }
241        }
242        if (currentResultLayout == null && resultLayouts != null && !resultLayouts.isEmpty()) {
243            // resolve first current result layout
244            return resultLayouts.get(0);
245        }
246        return currentResultLayout;
247    }
248
249    @Override
250    public void setCurrentResultLayout(final ContentViewLayout layout) {
251        setCurrentResultLayout(layout, true);
252    }
253
254    public void setCurrentResultLayout(final ContentViewLayout layout, boolean resetLayoutColumn) {
255        if (!isBlank(resultLayoutBinding) && ComponentTagUtils.isStrictValueReference(resultLayoutBinding)) {
256            resolveWithSearchDocument(ctx -> {
257                ComponentTagUtils.applyValueExpression(ctx, resultLayoutBinding,
258                        layout == null ? null : layout.getName());
259                return null;
260            });
261        }
262        // still set current result layout value
263        currentResultLayoutSet = true;
264        currentResultLayout = layout;
265
266        if (resetLayoutColumn) {
267            // reset corresponding columns
268            setCurrentResultLayoutColumns(null);
269        }
270    }
271
272    protected Object resolveWithSearchDocument(Function<FacesContext, Object> func) {
273        FacesContext ctx = FacesContext.getCurrentInstance();
274        if (getSearchDocumentModel() == null) {
275            return func.apply(ctx);
276        } else {
277            Object previousSearchDocValue = addSearchDocumentToELContext(ctx);
278            try {
279                return func.apply(ctx);
280            } finally {
281                removeSearchDocumentFromELContext(ctx, previousSearchDocValue);
282            }
283        }
284    }
285
286    @Override
287    public void setCurrentResultLayout(String resultLayoutName) {
288        if (resultLayoutName != null) {
289            for (ContentViewLayout layout : resultLayouts) {
290                if (resultLayoutName.equals(layout.getName())) {
291                    setCurrentResultLayout(layout, false);
292                }
293            }
294        }
295    }
296
297    @Override
298    public boolean hasResultLayoutBinding() {
299        return !isBlank(resultLayoutBinding);
300    }
301
302    /**
303     * Returns cached page provider if it exists or build a new one if parameters have changed.
304     * <p>
305     * The search document, current page and page size are set on the page provider anyway. Sort infos are not set again
306     * if page provider was not built again (e.g if parameters did not change) to avoid erasing sort infos already held
307     * by it.
308     */
309    @Override
310    public PageProvider<?> getPageProvider(DocumentModel searchDocument, List<SortInfo> sortInfos, Long pageSize,
311            Long currentPage, Object... params) {
312        // do not return any page provider if filter has not been done yet
313        if (isWaitForExecution() && !isExecuted()) {
314            return null;
315        }
316
317        // resolve search doc so that it can be used in EL expressions defined
318        // in XML configuration
319        boolean setSearchDoc = false;
320        DocumentModel finalSearchDocument = null;
321        if (searchDocument != null) {
322            setSearchDoc = true;
323            finalSearchDocument = searchDocument;
324        } else if (searchDocumentModel == null) {
325            setSearchDoc = true;
326            if (pageProvider != null) {
327                // try to retrieve it on current page provider
328                finalSearchDocument = pageProvider.getSearchDocumentModel();
329            }
330            if (finalSearchDocument == null) {
331                // initialize it and set it => do not need to set it again
332                finalSearchDocument = getSearchDocumentModel();
333                setSearchDoc = false;
334            }
335        } else {
336            finalSearchDocument = searchDocumentModel;
337        }
338        if (setSearchDoc) {
339            // set it on content view so that it can be used when resolving EL
340            // expressions
341            setSearchDocumentModel(finalSearchDocument);
342        }
343
344        // fallback on local parameters if defined in the XML configuration
345        if (params == null) {
346            params = getQueryParameters();
347        }
348        if (sortInfos == null) {
349            sortInfos = resolveSortInfos();
350        }
351        // allow to pass negative integers instead of null: EL transforms
352        // numbers into value 0 for numbers
353        if (pageSize != null && pageSize.longValue() < 0) {
354            pageSize = null;
355        }
356        if (currentPage != null && currentPage.longValue() < 0) {
357            currentPage = null;
358        }
359        if (pageSize == null) {
360            if (currentPageSize != null && currentPageSize.longValue() >= 0) {
361                pageSize = currentPageSize;
362            }
363            if (pageSize == null) {
364                pageSize = resolvePageSize();
365            }
366        }
367
368        // parameters changed => reset provider.
369        // do not force setting of sort infos as they can be set directly on
370        // the page provider and this method will be called after so they could
371        // be lost.
372        if (pageProvider == null || pageProvider.hasChangedParameters(params)) {
373            // make the service build the provider
374            ContentViewService service = Framework.getLocalService(ContentViewService.class);
375            pageProvider = service.getPageProvider(getName(), sortInfos, pageSize, currentPage, finalSearchDocument,
376                    params);
377        } else {
378            if (pageSize != null) {
379                pageProvider.setPageSize(pageSize.longValue());
380            }
381            if (currentPage != null) {
382                pageProvider.setCurrentPage(currentPage.longValue());
383            }
384        }
385
386        pageProvider.setQuickFilters(new ArrayList<>());
387
388        // Register listener to be notified when the page has changed on the
389        // page provider
390        pageProvider.setPageProviderChangedListener(this);
391        return pageProvider;
392    }
393
394    @Override
395    public PageProvider<?> getPageProviderWithParams(Object... params) {
396        return getPageProvider(null, null, null, null, params);
397    }
398
399    @Override
400    public PageProvider<?> getPageProvider() {
401        return getPageProviderWithParams((Object[]) null);
402    }
403
404    @Override
405    public PageProvider<?> getCurrentPageProvider() {
406        return pageProvider;
407    }
408
409    @Override
410    public void resetPageProvider() {
411        pageProvider = null;
412    }
413
414    @Override
415    public void refreshPageProvider() {
416        if (pageProvider != null) {
417            pageProvider.refresh();
418        }
419        setExecuted(true);
420    }
421
422    @Override
423    public void refreshAndRewindPageProvider() {
424        if (pageProvider != null) {
425            pageProvider.refresh();
426            pageProvider.firstPage();
427        }
428        setExecuted(true);
429    }
430
431    @Override
432    public String getCacheKey() {
433        FacesContext context = FacesContext.getCurrentInstance();
434        Object value = ComponentTagUtils.resolveElExpression(context, cacheKey);
435        if (value != null && !(value instanceof String)) {
436            log.error("Error processing expression '" + cacheKey + "', result is not a String: " + value);
437        }
438        return (String) value;
439    }
440
441    @Override
442    public Integer getCacheSize() {
443        return cacheSize;
444    }
445
446    @Override
447    public Object[] getQueryParameters() {
448        if (queryParameters == null) {
449            return null;
450        }
451        FacesContext context = FacesContext.getCurrentInstance();
452        Object previousSearchDocValue = addSearchDocumentToELContext(context);
453        try {
454            Object[] res = new Object[queryParameters.length];
455            for (int i = 0; i < queryParameters.length; i++) {
456                res[i] = ComponentTagUtils.resolveElExpression(context, queryParameters[i]);
457            }
458            return res;
459        } finally {
460            removeSearchDocumentFromELContext(context, previousSearchDocValue);
461        }
462    }
463
464    @Override
465    public List<String> getRefreshEventNames() {
466        return refreshEventNames;
467    }
468
469    @Override
470    public List<String> getResetEventNames() {
471        return resetEventNames;
472    }
473
474    @Override
475    public boolean getUseGlobalPageSize() {
476        return useGlobalPageSize;
477    }
478
479    @Override
480    public Long getCurrentPageSize() {
481        // take actual value on page provider first in case it's reached its
482        // max page size
483        if (pageProvider != null) {
484            long pageSize = pageProvider.getPageSize();
485            long maxPageSize = pageProvider.getMaxPageSize();
486            if (pageSize > 0 && maxPageSize > 0 && maxPageSize < pageSize) {
487                return Long.valueOf(maxPageSize);
488            }
489            return Long.valueOf(pageSize);
490        }
491        if (currentPageSize != null && currentPageSize.longValue() >= 0) {
492            return currentPageSize;
493        }
494        return null;
495    }
496
497    @Override
498    public void setCurrentPageSize(Long pageSize) {
499        currentPageSize = pageSize;
500        raiseEvent(CONTENT_VIEW_PAGE_SIZE_CHANGED_EVENT);
501    }
502
503    @Override
504    public DocumentModel getSearchDocumentModel() {
505        if (searchDocumentModel == null) {
506            if (searchDocumentModelBinding != null) {
507                // initialize from binding
508                FacesContext context = FacesContext.getCurrentInstance();
509                Object value = ComponentTagUtils.resolveElExpression(context, searchDocumentModelBinding);
510                if (value != null && !(value instanceof DocumentModel)) {
511                    log.error("Error processing expression '" + searchDocumentModelBinding
512                            + "', result is not a DocumentModel: " + value);
513                } else {
514                    setSearchDocumentModel((DocumentModel) value);
515                }
516            }
517            if (searchDocumentModel == null) {
518                // generate a bare document model of given type
519                String docType = getSearchDocumentModelType();
520                if (docType != null) {
521                    DocumentModel bareDoc = DocumentModelFactory.createDocumentModel(docType);
522                    setSearchDocumentModel(bareDoc);
523                }
524            }
525        }
526        return searchDocumentModel;
527    }
528
529    @Override
530    public void setSearchDocumentModel(DocumentModel searchDocumentModel) {
531        this.searchDocumentModel = searchDocumentModel;
532        if (pageProvider != null) {
533            pageProvider.setSearchDocumentModel(searchDocumentModel);
534        }
535    }
536
537    @Override
538    public void resetSearchDocumentModel() {
539        searchDocumentModel = null;
540        if (pageProvider != null) {
541            pageProvider.setSearchDocumentModel(null);
542        }
543    }
544
545    @Override
546    public String getSearchDocumentModelType() {
547        return searchDocumentModelType;
548    }
549
550    @Override
551    public List<String> getFlags() {
552        return flags;
553    }
554
555    @Override
556    @SuppressWarnings({ "unchecked", "rawtypes" })
557    public List<String> getCurrentResultLayoutColumns() {
558        // always resolve binding if it is set
559        if (!StringUtils.isBlank(resultColumnsBinding)) {
560            Object res = resolveWithSearchDocument(ctx -> {
561                Object value = ComponentTagUtils.resolveElExpression(ctx, resultColumnsBinding);
562                if (value != null && !(value instanceof List)) {
563                    log.error("Error processing expression '" + resultColumnsBinding + "', result is not a List: "
564                            + value);
565                }
566                return value;
567            });
568            if (res != null && res instanceof List) {
569                return ((List) res).isEmpty() ? null : (List) res;
570            }
571        }
572        return currentResultLayoutColumns;
573    }
574
575    @Override
576    public void setCurrentResultLayoutColumns(final List<String> resultColumns) {
577        if (isBlank(resultColumnsBinding) || !ComponentTagUtils.isStrictValueReference(resultColumnsBinding)) {
578            // set local values
579            currentResultLayoutColumns = resultColumns;
580        } else {
581            resolveWithSearchDocument(ctx -> {
582                ComponentTagUtils.applyValueExpression(ctx, resultColumnsBinding, resultColumns);
583                return null;
584            });
585        }
586    }
587
588    @Override
589    public boolean hasResultLayoutColumnsBinding() {
590        return !isBlank(resultColumnsBinding);
591    }
592
593    @SuppressWarnings({ "unchecked", "rawtypes" })
594    protected List<SortInfo> resolveSortInfos() {
595        if (sortInfosBinding == null) {
596            return null;
597        }
598        FacesContext context = FacesContext.getCurrentInstance();
599        Object previousSearchDocValue = addSearchDocumentToELContext(context);
600        try {
601            Object value = ComponentTagUtils.resolveElExpression(context, sortInfosBinding);
602            if (value != null && !(value instanceof List)) {
603                log.error("Error processing expression '" + sortInfosBinding + "', result is not a List: '" + value
604                        + "'");
605            }
606            if (value == null) {
607                return null;
608            }
609            List<SortInfo> res = new ArrayList<>();
610            List listValue = (List) value;
611            for (Object listItem : listValue) {
612                if (listItem instanceof SortInfo) {
613                    res.add((SortInfo) listItem);
614                } else if (listItem instanceof Map) {
615                    // XXX: MapProperty does not implement containsKey, so
616                    // resolve
617                    // value instead
618                    if (listItem instanceof MapProperty) {
619                        try {
620                            listItem = ((MapProperty) listItem).getValue();
621                        } catch (ClassCastException | PropertyException e) {
622                            log.error("Cannot resolve sort info item: " + listItem, e);
623                        }
624                    }
625                    Map map = (Map) listItem;
626                    SortInfo sortInfo = SortInfo.asSortInfo(map);
627                    if (sortInfo != null) {
628                        res.add(sortInfo);
629                    } else {
630                        log.error("Cannot resolve sort info item: " + listItem);
631                    }
632                } else {
633                    log.error("Cannot resolve sort info item: " + listItem);
634                }
635            }
636            if (res.isEmpty()) {
637                return null;
638            }
639            return res;
640        } finally {
641            removeSearchDocumentFromELContext(context, previousSearchDocValue);
642        }
643    }
644
645    protected Long resolvePageSize() {
646        if (pageSizeBinding == null) {
647            return null;
648        }
649        FacesContext context = FacesContext.getCurrentInstance();
650        Object previousSearchDocValue = addSearchDocumentToELContext(context);
651        try {
652            Object value = ComponentTagUtils.resolveElExpression(context, pageSizeBinding);
653            if (value == null) {
654                return null;
655            }
656            if (value instanceof String) {
657                try {
658                    return Long.valueOf((String) value);
659                } catch (NumberFormatException e) {
660                    log.error("Error processing expression '" + pageSizeBinding + "', result is not a Long: '" + value
661                            + "'");
662                }
663            } else if (value instanceof Number) {
664                return Long.valueOf(((Number) value).longValue());
665            }
666            return null;
667        } finally {
668            removeSearchDocumentFromELContext(context, previousSearchDocValue);
669        }
670    }
671
672    protected Object addSearchDocumentToELContext(FacesContext facesContext) {
673        ExternalContext econtext = null;
674        if (facesContext != null) {
675            econtext = facesContext.getExternalContext();
676        }
677        if (facesContext == null || econtext == null) {
678            log.error("JSF context is null: cannot expose variable '" + SEARCH_DOCUMENT_EL_VARIABLE
679                    + "' for content view '" + getName() + "'");
680            return null;
681        }
682        Map<String, Object> requestMap = econtext.getRequestMap();
683        Object previousValue = requestMap.get(SEARCH_DOCUMENT_EL_VARIABLE);
684        requestMap.put(SEARCH_DOCUMENT_EL_VARIABLE, searchDocumentModel);
685        return previousValue;
686    }
687
688    protected void removeSearchDocumentFromELContext(FacesContext facesContext, Object previousValue) {
689        if (facesContext == null) {
690            // ignore
691            return;
692        }
693        ExternalContext econtext = facesContext.getExternalContext();
694        if (econtext != null) {
695            Map<String, Object> requestMap = econtext.getRequestMap();
696            requestMap.remove(SEARCH_DOCUMENT_EL_VARIABLE);
697            if (previousValue != null) {
698                requestMap.put(SEARCH_DOCUMENT_EL_VARIABLE, previousValue);
699            }
700        } else {
701            log.error("External context is null: cannot dispose variable '" + SEARCH_DOCUMENT_EL_VARIABLE
702                    + "' for content view '" + getName() + "'");
703        }
704    }
705
706    @Override
707    public boolean getShowPageSizeSelector() {
708        return showPageSizeSelector;
709    }
710
711    @Override
712    public boolean getShowRefreshCommand() {
713        return showRefreshCommand;
714    }
715
716    @Override
717    public boolean getShowFilterForm() {
718        return showFilterForm;
719    }
720
721    @Override
722    public boolean getShowTitle() {
723        return showTitle;
724    }
725
726    @Override
727    public String getEmptySentence() {
728        return emptySentence;
729    }
730
731    @Override
732    public boolean getTranslateEmptySentence() {
733        return translateEmptySentence;
734    }
735
736    @Override
737    public String toString() {
738        final StringBuilder buf = new StringBuilder();
739        buf.append("ContentViewImpl")
740           .append(" {")
741           .append(" name=")
742           .append(name)
743           .append(", title=")
744           .append(title)
745           .append(", translateTitle=")
746           .append(translateTitle)
747           .append(", iconPath=")
748           .append(iconPath)
749           .append(", selectionList=")
750           .append(selectionList)
751           .append(", pagination=")
752           .append(pagination)
753           .append(", actionCategories=")
754           .append(actionCategories)
755           .append(", searchLayout=")
756           .append(searchLayout)
757           .append(", resultLayouts=")
758           .append(resultLayouts)
759           .append(", currentResultLayout=")
760           .append(currentResultLayout)
761           .append(", flags=")
762           .append(flags)
763           .append(", cacheKey=")
764           .append(cacheKey)
765           .append(", cacheSize=")
766           .append(cacheSize)
767           .append(", currentPageSize=")
768           .append(currentPageSize)
769           .append(", refreshEventNames=")
770           .append(refreshEventNames)
771           .append(", resetEventNames=")
772           .append(resetEventNames)
773           .append(", useGlobalPageSize=")
774           .append(useGlobalPageSize)
775           .append(", searchDocumentModel=")
776           .append(searchDocumentModel)
777           .append('}');
778        return buf.toString();
779    }
780
781    /*
782     * ----- PageProviderChangedListener -----
783     */
784
785    protected void raiseEvent(String eventName, Object... params) {
786        if (Events.exists()) {
787            Events.instance().raiseEvent(eventName, params);
788        }
789    }
790
791    protected void raiseEvent(String eventName) {
792        raiseEvent(eventName, name);
793    }
794
795    @Override
796    public void pageChanged(PageProvider<?> pageProvider) {
797        raiseEvent(CONTENT_VIEW_PAGE_CHANGED_EVENT);
798    }
799
800    @Override
801    public void refreshed(PageProvider<?> pageProvider) {
802        raiseEvent(CONTENT_VIEW_REFRESH_EVENT);
803    }
804
805    @SuppressWarnings("rawtypes")
806    @Override
807    public void resetPageProviderAggregates() {
808        if (pageProvider != null && pageProvider.hasAggregateSupport()) {
809            Map<String, ? extends Aggregate> aggs = pageProvider.getAggregates();
810            for (Aggregate agg : aggs.values()) {
811                agg.resetSelection();
812            }
813        }
814    }
815
816    @Override
817    public String getWaitForExecutionSentence() {
818        return waitForExecutionSentence;
819    }
820
821    /**
822     * @since 7.4
823     */
824    public void setWaitForExecutionSentence(String waitForExecutionSentence) {
825        this.waitForExecutionSentence = waitForExecutionSentence;
826    }
827
828    @Override
829    public boolean isWaitForExecution() {
830        return waitForExecution;
831    }
832
833    /**
834     * @since 7.4
835     */
836    public void setWaitForExecution(boolean waitForExecution) {
837        this.waitForExecution = waitForExecution;
838    }
839
840    @Override
841    public boolean isExecuted() {
842        return executed;
843    }
844
845    @Override
846    public void setExecuted(boolean executed) {
847        this.executed = executed;
848    }
849
850    /**
851     * @since 8.4
852     */
853    public void setQuickFilters(List<QuickFilter> quickFilters) {
854        this.quickFilters = quickFilters;
855    }
856
857    /**
858     * @since 8.4
859     */
860    public List<QuickFilter> getQuickFilters() {
861        return this.quickFilters;
862    }
863}