001/*
002 * (C) Copyright 2014 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 *     Thomas Roger
018 */
019
020package org.nuxeo.search.ui.seam;
021
022import static org.apache.commons.lang.StringUtils.isNotBlank;
023import static org.apache.commons.logging.LogFactory.getLog;
024import static org.jboss.seam.ScopeType.CONVERSATION;
025import static org.jboss.seam.annotations.Install.FRAMEWORK;
026import static org.nuxeo.ecm.webapp.helpers.EventNames.LOCAL_CONFIGURATION_CHANGED;
027import static org.nuxeo.ecm.webapp.helpers.EventNames.USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED;
028
029import java.io.Serializable;
030import java.io.UnsupportedEncodingException;
031import java.util.ArrayList;
032import java.util.List;
033import java.util.Map;
034
035import javax.faces.application.FacesMessage;
036import javax.faces.component.UIComponent;
037import javax.faces.component.UIViewRoot;
038import javax.faces.context.FacesContext;
039import javax.faces.model.SelectItem;
040import javax.faces.model.SelectItemGroup;
041import javax.faces.validator.ValidatorException;
042
043import org.apache.commons.lang.StringUtils;
044import org.apache.commons.logging.Log;
045import org.jboss.seam.annotations.Begin;
046import org.jboss.seam.annotations.In;
047import org.jboss.seam.annotations.Install;
048import org.jboss.seam.annotations.Name;
049import org.jboss.seam.annotations.Observer;
050import org.jboss.seam.annotations.Scope;
051import org.jboss.seam.annotations.intercept.BypassInterceptors;
052import org.jboss.seam.core.Events;
053import org.jboss.seam.faces.FacesMessages;
054import org.jboss.seam.international.StatusMessage;
055import org.nuxeo.ecm.core.api.CoreSession;
056import org.nuxeo.ecm.core.api.DocumentModel;
057import org.nuxeo.ecm.core.api.IdRef;
058import org.nuxeo.ecm.core.api.PathRef;
059import org.nuxeo.ecm.core.api.impl.DocumentLocationImpl;
060import org.nuxeo.ecm.platform.contentview.jsf.ContentView;
061import org.nuxeo.ecm.platform.contentview.jsf.ContentViewHeader;
062import org.nuxeo.ecm.platform.contentview.jsf.ContentViewService;
063import org.nuxeo.ecm.platform.contentview.jsf.ContentViewState;
064import org.nuxeo.ecm.platform.contentview.jsf.ContentViewStateImpl;
065import org.nuxeo.ecm.platform.contentview.json.JSONContentViewState;
066import org.nuxeo.ecm.platform.contentview.seam.ContentViewActions;
067import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
068import org.nuxeo.ecm.platform.ui.web.api.WebActions;
069import org.nuxeo.ecm.platform.ui.web.rest.RestHelper;
070import org.nuxeo.ecm.platform.ui.web.util.BaseURL;
071import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils;
072import org.nuxeo.ecm.platform.url.DocumentViewImpl;
073import org.nuxeo.ecm.platform.url.api.DocumentView;
074import org.nuxeo.ecm.platform.url.api.DocumentViewCodecManager;
075import org.nuxeo.ecm.webapp.action.ActionContextProvider;
076import org.nuxeo.ecm.webapp.documentsLists.DocumentsListsManager;
077import org.nuxeo.ecm.webapp.helpers.EventNames;
078import org.nuxeo.runtime.api.Framework;
079import org.nuxeo.search.ui.SearchUIService;
080
081/**
082 * Seam bean handling Search main tab actions.
083 *
084 * @since 6.0
085 */
086@Name("searchUIActions")
087@Scope(CONVERSATION)
088@Install(precedence = FRAMEWORK)
089public class SearchUIActions implements Serializable {
090
091    private static final long serialVersionUID = 1L;
092
093    private static final Log log = getLog(SearchUIActions.class);
094
095    public static final String SAVED_SEARCHES_LABEL = "label.saved.searches";
096
097    public static final String SHARED_SEARCHES_LABEL = "label.shared.searches";
098
099    public static final String SEARCH_FILTERS_LABEL = "label.search.filters";
100
101    public static final String SEARCH_SAVED_LABEL = "label.search.saved";
102
103    public static final String MAIN_TABS_SEARCH = "MAIN_TABS:search";
104
105    public static final String SEARCH_VIEW_ID = "/search/search.xhtml";
106
107    public static final String SEARCH_CODEC = "docpathsearch";
108
109    public static final String SIMPLE_SEARCH_CONTENT_VIEW_NAME = "simple_search";
110
111    public static final String NXQL_SEARCH_CONTENT_VIEW_NAME = "nxql_search";
112
113    public static final String DEFAULT_NXQL_QUERY = "SELECT * FROM Document"
114            + " WHERE ecm:mixinType != 'HiddenInNavigation'" + " AND ecm:isProxy = 0 AND ecm:isCheckedInVersion = 0"
115            + " AND ecm:currentLifeCycleState != 'deleted'";
116
117    public static final String CONTENT_VIEW_NAME_PARAMETER = "contentViewName";
118
119    public static final String CURRENT_PAGE_PARAMETER = "currentPage";
120
121    public static final String PAGE_SIZE_PARAMETER = "pageSize";
122
123    public static final String CONTENT_VIEW_STATE_PARAMETER = "state";
124
125    public static final String SEARCH_TERM_PARAMETER = "searchTerm";
126
127    /**
128     * Event name for search selection change, raised with selected corresponding content view name.
129     *
130     * @since 8.1
131     */
132    public static final String SEARCH_SELECTED_EVENT = "searchSelected";
133
134    /**
135     * Event name for search selection change, raised with saved document model.
136     *
137     * @since 8.1
138     */
139    public static final String SEARCH_SAVED_EVENT = "searchSaved";
140
141    @In(create = true)
142    protected transient NavigationContext navigationContext;
143
144    @In(create = true, required = false)
145    protected transient CoreSession documentManager;
146
147    @In(create = true, required = false)
148    protected transient ActionContextProvider actionContextProvider;
149
150    @In(create = true)
151    protected transient WebActions webActions;
152
153    @In(create = true)
154    protected RestHelper restHelper;
155
156    @In(create = true)
157    protected ContentViewActions contentViewActions;
158
159    @In(create = true)
160    protected ContentViewService contentViewService;
161
162    @In(create = true)
163    protected DocumentsListsManager documentsListsManager;
164
165    @In(create = true, required = false)
166    protected FacesMessages facesMessages;
167
168    @In(create = true)
169    protected Map<String, String> messages;
170
171    protected String simpleSearchKeywords = "";
172
173    protected String nxqlQuery = DEFAULT_NXQL_QUERY;
174
175    protected List<ContentViewHeader> contentViewHeaders;
176
177    protected String currentContentViewName;
178
179    protected String currentSelectedSavedSearchId;
180
181    protected String currentPage;
182
183    protected String pageSize;
184
185    protected String searchTerm;
186
187    protected String savedSearchTitle;
188
189    public String getSearchMainTab() {
190        return MAIN_TABS_SEARCH;
191    }
192
193    public void setSearchMainTab(String tabs) {
194        webActions.setCurrentTabIds(!StringUtils.isBlank(tabs) ? tabs : MAIN_TABS_SEARCH);
195    }
196
197    public String getSearchViewTitle() {
198        if (currentSelectedSavedSearchId != null) {
199            DocumentModel savedSearch = documentManager.getDocument(new IdRef(currentSelectedSavedSearchId));
200            return savedSearch.getTitle();
201        } else if (currentContentViewName != null) {
202            ContentView cv = contentViewActions.getContentView(currentContentViewName);
203            String title = cv.getTranslateTitle() ? messages.get(cv.getTitle()) : cv.getTitle();
204            return isNotBlank(title) ? title : currentContentViewName;
205        }
206        return null;
207    }
208
209    /**
210     * Returns true if the user is viewing SEARCH.
211     */
212    public boolean isOnSearchView() {
213        if (FacesContext.getCurrentInstance() == null) {
214            return false;
215        }
216
217        UIViewRoot viewRoot = FacesContext.getCurrentInstance().getViewRoot();
218        if (viewRoot != null) {
219            String viewId = viewRoot.getViewId();
220            // FIXME find a better way to update the current document only
221            // if we are on SEARCH
222            if (SEARCH_VIEW_ID.equals(viewId)) {
223                return true;
224            }
225        }
226        return false;
227    }
228
229    public String getJSONContentViewState() throws UnsupportedEncodingException {
230        ContentView contentView = contentViewActions.getContentView(currentContentViewName);
231        ContentViewService contentViewService = Framework.getService(ContentViewService.class);
232        ContentViewState state = contentViewService.saveContentView(contentView);
233        return JSONContentViewState.toJSON(state, true);
234    }
235
236    public String getCurrentContentViewName() {
237        if (currentContentViewName == null) {
238            List<ContentViewHeader> contentViewHeaders = getContentViewHeaders();
239            if (!contentViewHeaders.isEmpty()) {
240                currentContentViewName = contentViewHeaders.get(0).getName();
241            }
242        }
243        return currentContentViewName;
244    }
245
246    public void setCurrentContentViewName(String contentViewName) {
247        this.currentContentViewName = contentViewName;
248    }
249
250    public String getCurrentSelectedSavedSearchId() {
251        return currentSelectedSavedSearchId != null ? currentSelectedSavedSearchId : currentContentViewName;
252    }
253
254    public void setCurrentSelectedSavedSearchId(String selectedSavedSearchId) throws UnsupportedEncodingException {
255        resetCurrentContentViewWorkingList();
256
257        for (ContentViewHeader contentViewHeader : contentViewHeaders) {
258            if (contentViewHeader.getName().equals(selectedSavedSearchId)) {
259                contentViewActions.reset(currentContentViewName);
260                currentContentViewName = selectedSavedSearchId;
261                Events.instance().raiseEvent(SEARCH_SELECTED_EVENT, currentContentViewName);
262                currentSelectedSavedSearchId = null;
263                return;
264            }
265        }
266        DocumentModel savedSearch = documentManager.getDocument(new IdRef(selectedSavedSearchId));
267        loadSavedSearch(savedSearch);
268    }
269
270    protected void resetCurrentContentViewWorkingList() {
271        if (currentContentViewName != null) {
272            ContentView contentView = contentViewActions.getContentView(currentContentViewName);
273            documentsListsManager.resetWorkingList(contentView.getSelectionListName());
274        }
275    }
276
277    public void loadSavedSearch(DocumentModel searchDocument) throws UnsupportedEncodingException {
278        SearchUIService searchUIService = Framework.getService(SearchUIService.class);
279        ContentViewState contentViewState = searchUIService.loadSearch(searchDocument);
280        if (contentViewState != null) {
281            ContentView contentView = contentViewActions.restoreContentView(contentViewState);
282            currentContentViewName = contentView.getName();
283            Events.instance().raiseEvent(SEARCH_SELECTED_EVENT, currentContentViewName);
284        }
285        currentSelectedSavedSearchId = searchDocument.getId();
286    }
287
288    public List<ContentViewHeader> getContentViewHeaders() {
289        if (contentViewHeaders == null) {
290            SearchUIService searchUIService = Framework.getService(SearchUIService.class);
291            contentViewHeaders = searchUIService.getContentViewHeaders(actionContextProvider.createActionContext(),
292                    navigationContext.getCurrentDocument());
293        }
294        return contentViewHeaders;
295    }
296
297    public void clearSearch() {
298        if (currentContentViewName != null) {
299            contentViewActions.reset(currentContentViewName);
300            resetCurrentContentViewWorkingList();
301        }
302    }
303
304    public void refreshAndRewind() {
305        String contentViewName = getCurrentContentViewName();
306        if (contentViewName != null) {
307            contentViewActions.refreshAndRewind(contentViewName);
308            resetCurrentContentViewWorkingList();
309        }
310    }
311
312    public void refreshAndRewindAndResetAggregates() {
313        contentViewActions.resetAggregates(getCurrentContentViewName());
314        refreshAndRewind();
315    }
316
317    /*
318     * ----- Load / Save searches -----
319     */
320
321    public List<SelectItem> getAllSavedSearchesSelectItems() {
322        List<SelectItem> items = new ArrayList<>();
323
324        // Add flagged content views
325        SelectItemGroup flaggedGroup = new SelectItemGroup(messages.get(SEARCH_FILTERS_LABEL));
326        List<ContentViewHeader> flaggedSavedSearches = getContentViewHeaders();
327        List<SelectItem> flaggedSavedSearchesItems = convertCVToSelectItems(flaggedSavedSearches);
328        flaggedGroup.setSelectItems(flaggedSavedSearchesItems.toArray(new SelectItem[flaggedSavedSearchesItems.size()]));
329        items.add(flaggedGroup);
330
331        // Add saved searches
332        List<DocumentModel> userSavedSearches = getSavedSearches();
333        if (!userSavedSearches.isEmpty()) {
334            SelectItemGroup userGroup = new SelectItemGroup(messages.get(SAVED_SEARCHES_LABEL));
335
336            List<SelectItem> userSavedSearchesItems = convertToSelectItems(userSavedSearches);
337            userGroup.setSelectItems(userSavedSearchesItems.toArray(new SelectItem[userSavedSearchesItems.size()]));
338            items.add(userGroup);
339        }
340
341        // Add shared searches
342        List<DocumentModel> otherUsersSavedFacetedSearches = getSharedSearches();
343        if (!otherUsersSavedFacetedSearches.isEmpty()) {
344            List<SelectItem> otherUsersSavedSearchesItems = convertToSelectItems(otherUsersSavedFacetedSearches);
345            SelectItemGroup allGroup = new SelectItemGroup(messages.get(SHARED_SEARCHES_LABEL));
346            allGroup.setSelectItems(otherUsersSavedSearchesItems.toArray(new SelectItem[otherUsersSavedSearchesItems.size()]));
347            items.add(allGroup);
348        }
349        return items;
350    }
351
352    protected List<DocumentModel> getSavedSearches() {
353        SearchUIService searchUIService = Framework.getService(SearchUIService.class);
354        return searchUIService.getCurrentUserSavedSearches(documentManager);
355    }
356
357    protected List<DocumentModel> getSharedSearches() {
358        SearchUIService searchUIService = Framework.getService(SearchUIService.class);
359        return searchUIService.getSharedSavedSearches(documentManager);
360    }
361
362    protected List<SelectItem> convertToSelectItems(List<DocumentModel> docs) {
363        List<SelectItem> items = new ArrayList<>();
364        for (DocumentModel doc : docs) {
365            items.add(new SelectItem(doc.getId(), doc.getTitle(), ""));
366        }
367        return items;
368    }
369
370    protected List<SelectItem> convertCVToSelectItems(List<ContentViewHeader> contentViewHeaders) {
371        List<SelectItem> items = new ArrayList<>();
372        for (ContentViewHeader contentViewHeader : contentViewHeaders) {
373            items.add(new SelectItem(contentViewHeader.getName(), messages.get(contentViewHeader.getTitle()), ""));
374        }
375        return items;
376    }
377
378    public String getSavedSearchTitle() {
379        return savedSearchTitle;
380    }
381
382    public void setSavedSearchTitle(String savedSearchTitle) {
383        this.savedSearchTitle = savedSearchTitle;
384    }
385
386    public String saveSearch() {
387        ContentView contentView = contentViewActions.getContentView(getCurrentContentViewName());
388        if (contentView != null) {
389            ContentViewState state = contentViewService.saveContentView(contentView);
390            SearchUIService searchUIService = Framework.getService(SearchUIService.class);
391            DocumentModel savedSearch = searchUIService.saveSearch(documentManager, state, savedSearchTitle);
392            currentSelectedSavedSearchId = savedSearch.getId();
393
394            Events.instance().raiseEvent(SEARCH_SAVED_EVENT, savedSearch);
395
396            savedSearchTitle = null;
397            facesMessages.add(StatusMessage.Severity.INFO, messages.get(SEARCH_SAVED_LABEL));
398        }
399        return null;
400    }
401
402    /**
403     * Retsurns true if current search can be saved.
404     * <p>
405     * Returns false if current content view is waiting for a first execution.
406     *
407     * @since 7.4
408     */
409    public boolean getCanSaveSearch() {
410        ContentView contentView = contentViewActions.getContentView(getCurrentContentViewName());
411        if (contentView != null) {
412            boolean res = !contentView.isWaitForExecution() || contentView.isExecuted();
413            return res;
414        }
415        return false;
416    }
417
418    public void cancelSaveSearch() {
419        savedSearchTitle = null;
420    }
421
422    /*
423     * ----- Permanent links -----
424     */
425
426    public void setState(String state) throws UnsupportedEncodingException {
427        if (isNotBlank(state)) {
428            Long finalPageSize = null;
429            if (!StringUtils.isBlank(pageSize)) {
430                try {
431                    finalPageSize = Long.valueOf(pageSize);
432                } catch (NumberFormatException e) {
433                    log.warn(String.format("Unable to parse '%s' parameter with value '%s'", PAGE_SIZE_PARAMETER,
434                            pageSize));
435                }
436            }
437
438            Long finalCurrentPage = null;
439            if (!StringUtils.isBlank(currentPage)) {
440                try {
441                    finalCurrentPage = Long.valueOf(currentPage);
442                } catch (NumberFormatException e) {
443                    log.warn(String.format("Unable to parse '%s' parameter with value '%s'", CURRENT_PAGE_PARAMETER,
444                            currentPage));
445                }
446            }
447
448            String cvName = getCurrentContentViewName();
449            List<ContentViewHeader> contentViewHeaders = getContentViewHeaders();
450            if (cvName != null && contentViewHeaders != null) {
451                boolean canRestore = false;
452                for (ContentViewHeader contentViewHeader : getContentViewHeaders()) {
453                    if (cvName.equals(contentViewHeader.getName())) {
454                        canRestore = true;
455                    }
456                }
457
458                if (canRestore) {
459                    contentViewActions.restoreContentView(getCurrentContentViewName(), finalCurrentPage, finalPageSize,
460                            null, state);
461                } else {
462                    invalidateContentViewsName();
463                }
464            }
465        }
466    }
467
468    public String getCurrentPage() {
469        return currentPage;
470    }
471
472    public void setCurrentPage(String currentPage) {
473        this.currentPage = currentPage;
474    }
475
476    public String getPageSize() {
477        return pageSize;
478    }
479
480    public void setPageSize(String pageSize) {
481        this.pageSize = pageSize;
482    }
483
484    public void setSearchTerm(String searchTerm) throws UnsupportedEncodingException {
485        // If the search term is not defined, we don't do the logic
486        if (!StringUtils.isEmpty(searchTerm)) {
487            // By default, the "simple_search" content view is used
488            currentContentViewName = SIMPLE_SEARCH_CONTENT_VIEW_NAME;
489            // Create a ContentViewState
490            ContentView cv = contentViewService.getContentView(SIMPLE_SEARCH_CONTENT_VIEW_NAME);
491            DocumentModel searchDocumentModel = cv.getSearchDocumentModel();
492            // set the search term
493            searchDocumentModel.setPropertyValue("default_search:ecm_fulltext", searchTerm);
494            ContentViewState state = new ContentViewStateImpl();
495            state.setSearchDocumentModel(searchDocumentModel);
496            state.setContentViewName(getCurrentContentViewName());
497            ContentView ccv = contentViewActions.restoreContentView(state);
498            ccv.setExecuted(true);
499        }
500    }
501
502    /**
503     * Compute a permanent link for the current search.
504     */
505    public String getSearchPermanentLinkUrl() throws UnsupportedEncodingException {
506        // do not try to compute an URL if we don't have any CoreSession
507        if (documentManager == null) {
508            return null;
509        }
510
511        return generateSearchUrl(true);
512    }
513
514    /**
515     * @return the URL of the search tab with the search term defined.
516     */
517    public String getSearchTabUrl(String searchTerm) throws UnsupportedEncodingException {
518        // do not try to compute an URL if we don't have any CoreSession
519        if (documentManager == null) {
520            return null;
521        }
522        // Set the value of the searched term
523        this.searchTerm = searchTerm;
524
525        return generateSearchUrl(false);
526    }
527
528    /**
529     * Create the url to access the Search tab.
530     *
531     * @param withState If set to true, the state is added in the parameters.
532     */
533    protected String generateSearchUrl(boolean withState) throws UnsupportedEncodingException {
534        String currentContentViewName = getCurrentContentViewName();
535        DocumentModel currentDocument = navigationContext.getCurrentDocument();
536        DocumentView docView = computeDocumentView(currentDocument);
537        docView.setViewId("search");
538        docView.addParameter(CONTENT_VIEW_NAME_PARAMETER, currentContentViewName);
539        // Add the state if needed
540        if (withState) {
541            docView.addParameter(CONTENT_VIEW_STATE_PARAMETER, getJSONContentViewState());
542        }
543
544        DocumentViewCodecManager documentViewCodecManager = Framework.getService(DocumentViewCodecManager.class);
545        String url = documentViewCodecManager.getUrlFromDocumentView(SEARCH_CODEC, docView, true, BaseURL.getBaseURL());
546
547        return RestHelper.addCurrentConversationParameters(url);
548    }
549
550    protected DocumentView computeDocumentView(DocumentModel doc) {
551        return new DocumentViewImpl(new DocumentLocationImpl(documentManager.getRepositoryName(),
552                doc != null ? new PathRef(doc.getPathAsString()) : null));
553    }
554
555    /*
556     * ----- Simple Search -----
557     */
558    public String getSimpleSearchKeywords() {
559        return simpleSearchKeywords;
560    }
561
562    public void setSimpleSearchKeywords(String simpleSearchKeywords) {
563        this.simpleSearchKeywords = simpleSearchKeywords;
564    }
565
566    public void validateSimpleSearchKeywords(FacesContext context, UIComponent component, Object value) {
567        if (!(value instanceof String) || StringUtils.isEmpty(((String) value).trim())) {
568            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, ComponentUtils.translate(context,
569                    "feedback.search.noKeywords"), null);
570            // also add global message
571            context.addMessage(null, message);
572            throw new ValidatorException(message);
573        }
574        String[] keywords = ((String) value).trim().split(" ");
575        for (String keyword : keywords) {
576            if (keyword.startsWith("*")) {
577                // Can't begin search with * character
578                FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, ComponentUtils.translate(context,
579                        "feedback.search.star"), null);
580                // also add global message
581                context.addMessage(null, message);
582                throw new ValidatorException(message);
583            }
584        }
585    }
586
587    public String doSimpleSearch() {
588        setSearchMainTab(null);
589        currentContentViewName = SIMPLE_SEARCH_CONTENT_VIEW_NAME;
590        ContentView contentView = contentViewActions.getContentView(SIMPLE_SEARCH_CONTENT_VIEW_NAME);
591        DocumentModel searchDoc = contentView.getSearchDocumentModel();
592        searchDoc.setPropertyValue("defaults:ecm_fulltext", simpleSearchKeywords);
593        refreshAndRewind();
594        return "search";
595    }
596
597    /*
598     * ----- NXQL Search -----
599     */
600    public String getNxqlQuery() {
601        return nxqlQuery;
602    }
603
604    public void setNxqlQuery(String nxqlQuery) {
605        this.nxqlQuery = nxqlQuery;
606    }
607
608    public boolean isNxqlSearchSelected() {
609        return NXQL_SEARCH_CONTENT_VIEW_NAME.equals(currentContentViewName);
610    }
611
612    @Begin(id = "#{conversationIdGenerator.currentOrNewMainConversationId}", join = true)
613    public String loadPermanentLink(DocumentView docView) {
614        restHelper.initContextFromRestRequest(docView);
615        return "search";
616    }
617
618    @Observer(value = LOCAL_CONFIGURATION_CHANGED)
619    public void invalidateContentViewsName() {
620        clearSearch();
621        contentViewHeaders = null;
622        currentContentViewName = null;
623    }
624
625    @Observer(value = USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED)
626    public void invalidateContentViewsNameIfChanged() {
627        List<ContentViewHeader> temp = new ArrayList<>(
628                Framework.getLocalService(SearchUIService.class).getContentViewHeaders(
629                        actionContextProvider.createActionContext(), navigationContext.getCurrentDocument()));
630        if (temp != null) {
631            if (!temp.equals(contentViewHeaders)) {
632                invalidateContentViewsName();
633            }
634            if (!temp.isEmpty()) {
635                String s = temp.get(0).getName();
636                if (s != null && !s.equals(currentContentViewName)) {
637                    invalidateContentViewsName();
638                }
639            }
640        }
641    }
642
643    /**
644     * Reset attributes.
645     */
646    @Observer(value = { EventNames.FLUSH_EVENT }, create = false)
647    @BypassInterceptors
648    public void resetOnFlush() {
649        contentViewHeaders = null;
650        currentSelectedSavedSearchId = null;
651        currentContentViewName = null;
652        nxqlQuery = DEFAULT_NXQL_QUERY;
653        simpleSearchKeywords = "";
654    }
655
656    public String getSearchTermParameter() {
657        return SEARCH_TERM_PARAMETER;
658    }
659
660    /**
661     * Triggers content view refresh/reset on saved search.
662     *
663     * @since 8.1
664     */
665    @Observer(value = { SEARCH_SAVED_EVENT })
666    public void onSearchSaved() {
667        contentViewActions.refreshOnSeamEvent(SEARCH_SAVED_EVENT);
668        contentViewActions.resetPageProviderOnSeamEvent(SEARCH_SAVED_EVENT);
669    }
670
671}