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