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            if (contentView != null) {
274                documentsListsManager.resetWorkingList(contentView.getSelectionListName());
275            }
276        }
277    }
278
279    public void loadSavedSearch(DocumentModel searchDocument) throws UnsupportedEncodingException {
280        SearchUIService searchUIService = Framework.getService(SearchUIService.class);
281        ContentViewState contentViewState = searchUIService.loadSearch(searchDocument);
282        if (contentViewState != null) {
283            ContentView contentView = contentViewActions.restoreContentView(contentViewState);
284            currentContentViewName = contentView.getName();
285            Events.instance().raiseEvent(SEARCH_SELECTED_EVENT, currentContentViewName);
286        }
287        currentSelectedSavedSearchId = searchDocument.getId();
288    }
289
290    public List<ContentViewHeader> getContentViewHeaders() {
291        if (contentViewHeaders == null) {
292            SearchUIService searchUIService = Framework.getService(SearchUIService.class);
293            contentViewHeaders = searchUIService.getContentViewHeaders(actionContextProvider.createActionContext(),
294                    navigationContext.getCurrentDocument());
295        }
296        return contentViewHeaders;
297    }
298
299    public void clearSearch() {
300        if (currentContentViewName != null) {
301            contentViewActions.reset(currentContentViewName);
302            resetCurrentContentViewWorkingList();
303        }
304    }
305
306    public void refreshAndRewind() {
307        String contentViewName = getCurrentContentViewName();
308        if (contentViewName != null) {
309            contentViewActions.refreshAndRewind(contentViewName);
310            resetCurrentContentViewWorkingList();
311        }
312    }
313
314    public void refreshAndRewindAndResetAggregates() {
315        contentViewActions.resetAggregates(getCurrentContentViewName());
316        refreshAndRewind();
317    }
318
319    /*
320     * ----- Load / Save searches -----
321     */
322
323    public List<SelectItem> getAllSavedSearchesSelectItems() {
324        List<SelectItem> items = new ArrayList<>();
325
326        // Add flagged content views
327        SelectItemGroup flaggedGroup = new SelectItemGroup(messages.get(SEARCH_FILTERS_LABEL));
328        List<ContentViewHeader> flaggedSavedSearches = getContentViewHeaders();
329        List<SelectItem> flaggedSavedSearchesItems = convertCVToSelectItems(flaggedSavedSearches);
330        flaggedGroup.setSelectItems(
331                flaggedSavedSearchesItems.toArray(new SelectItem[flaggedSavedSearchesItems.size()]));
332        items.add(flaggedGroup);
333
334        // Add saved searches
335        List<DocumentModel> userSavedSearches = getSavedSearches();
336        if (!userSavedSearches.isEmpty()) {
337            SelectItemGroup userGroup = new SelectItemGroup(messages.get(SAVED_SEARCHES_LABEL));
338
339            List<SelectItem> userSavedSearchesItems = convertToSelectItems(userSavedSearches);
340            userGroup.setSelectItems(userSavedSearchesItems.toArray(new SelectItem[userSavedSearchesItems.size()]));
341            items.add(userGroup);
342        }
343
344        // Add shared searches
345        List<DocumentModel> otherUsersSavedFacetedSearches = getSharedSearches();
346        if (!otherUsersSavedFacetedSearches.isEmpty()) {
347            List<SelectItem> otherUsersSavedSearchesItems = convertToSelectItems(otherUsersSavedFacetedSearches);
348            SelectItemGroup allGroup = new SelectItemGroup(messages.get(SHARED_SEARCHES_LABEL));
349            allGroup.setSelectItems(
350                    otherUsersSavedSearchesItems.toArray(new SelectItem[otherUsersSavedSearchesItems.size()]));
351            items.add(allGroup);
352        }
353        return items;
354    }
355
356    protected List<DocumentModel> getSavedSearches() {
357        SearchUIService searchUIService = Framework.getService(SearchUIService.class);
358        return searchUIService.getCurrentUserSavedSearches(documentManager);
359    }
360
361    protected List<DocumentModel> getSharedSearches() {
362        SearchUIService searchUIService = Framework.getService(SearchUIService.class);
363        return searchUIService.getSharedSavedSearches(documentManager);
364    }
365
366    protected List<SelectItem> convertToSelectItems(List<DocumentModel> docs) {
367        List<SelectItem> items = new ArrayList<>();
368        for (DocumentModel doc : docs) {
369            items.add(new SelectItem(doc.getId(), doc.getTitle(), ""));
370        }
371        return items;
372    }
373
374    protected List<SelectItem> convertCVToSelectItems(List<ContentViewHeader> contentViewHeaders) {
375        List<SelectItem> items = new ArrayList<>();
376        for (ContentViewHeader contentViewHeader : contentViewHeaders) {
377            items.add(new SelectItem(contentViewHeader.getName(), messages.get(contentViewHeader.getTitle()), ""));
378        }
379        return items;
380    }
381
382    public String getSavedSearchTitle() {
383        return savedSearchTitle;
384    }
385
386    public void setSavedSearchTitle(String savedSearchTitle) {
387        this.savedSearchTitle = savedSearchTitle;
388    }
389
390    public String saveSearch() {
391        ContentView contentView = contentViewActions.getContentView(getCurrentContentViewName());
392        if (contentView != null) {
393            ContentViewState state = contentViewService.saveContentView(contentView);
394            SearchUIService searchUIService = Framework.getService(SearchUIService.class);
395            DocumentModel savedSearch = searchUIService.saveSearch(documentManager, state, savedSearchTitle);
396            currentSelectedSavedSearchId = savedSearch.getId();
397
398            Events.instance().raiseEvent(SEARCH_SAVED_EVENT, savedSearch);
399
400            savedSearchTitle = null;
401            facesMessages.add(StatusMessage.Severity.INFO, messages.get(SEARCH_SAVED_LABEL));
402        }
403        return null;
404    }
405
406    /**
407     * Retsurns true if current search can be saved.
408     * <p>
409     * Returns false if current content view is waiting for a first execution.
410     *
411     * @since 7.4
412     */
413    public boolean getCanSaveSearch() {
414        ContentView contentView = contentViewActions.getContentView(getCurrentContentViewName());
415        if (contentView != null) {
416            boolean res = !contentView.isWaitForExecution() || contentView.isExecuted();
417            return res;
418        }
419        return false;
420    }
421
422    public void cancelSaveSearch() {
423        savedSearchTitle = null;
424    }
425
426    /*
427     * ----- Permanent links -----
428     */
429
430    public void setState(String state) throws UnsupportedEncodingException {
431        if (isNotBlank(state)) {
432            Long finalPageSize = null;
433            if (!StringUtils.isBlank(pageSize)) {
434                try {
435                    finalPageSize = Long.valueOf(pageSize);
436                } catch (NumberFormatException e) {
437                    log.warn(String.format("Unable to parse '%s' parameter with value '%s'", PAGE_SIZE_PARAMETER,
438                            pageSize));
439                }
440            }
441
442            Long finalCurrentPage = null;
443            if (!StringUtils.isBlank(currentPage)) {
444                try {
445                    finalCurrentPage = Long.valueOf(currentPage);
446                } catch (NumberFormatException e) {
447                    log.warn(String.format("Unable to parse '%s' parameter with value '%s'", CURRENT_PAGE_PARAMETER,
448                            currentPage));
449                }
450            }
451
452            String cvName = getCurrentContentViewName();
453            List<ContentViewHeader> contentViewHeaders = getContentViewHeaders();
454            if (cvName != null && contentViewHeaders != null) {
455                boolean canRestore = false;
456                for (ContentViewHeader contentViewHeader : getContentViewHeaders()) {
457                    if (cvName.equals(contentViewHeader.getName())) {
458                        canRestore = true;
459                    }
460                }
461
462                if (canRestore) {
463                    contentViewActions.restoreContentView(getCurrentContentViewName(), finalCurrentPage, finalPageSize,
464                            null, state);
465                } else {
466                    invalidateContentViewsName();
467                }
468            }
469        }
470    }
471
472    public String getCurrentPage() {
473        return currentPage;
474    }
475
476    public void setCurrentPage(String currentPage) {
477        this.currentPage = currentPage;
478    }
479
480    public String getPageSize() {
481        return pageSize;
482    }
483
484    public void setPageSize(String pageSize) {
485        this.pageSize = pageSize;
486    }
487
488    public void setSearchTerm(String searchTerm) throws UnsupportedEncodingException {
489        // If the search term is not defined, we don't do the logic
490        if (!StringUtils.isEmpty(searchTerm)) {
491            // By default, the "simple_search" content view is used
492            currentContentViewName = SIMPLE_SEARCH_CONTENT_VIEW_NAME;
493            // Create a ContentViewState
494            ContentView cv = contentViewService.getContentView(SIMPLE_SEARCH_CONTENT_VIEW_NAME);
495            DocumentModel searchDocumentModel = cv.getSearchDocumentModel();
496            // set the search term
497            searchDocumentModel.setPropertyValue("default_search:ecm_fulltext", searchTerm);
498            ContentViewState state = new ContentViewStateImpl();
499            state.setSearchDocumentModel(searchDocumentModel);
500            state.setContentViewName(getCurrentContentViewName());
501            ContentView ccv = contentViewActions.restoreContentView(state);
502            ccv.setExecuted(true);
503        }
504    }
505
506    /**
507     * Compute a permanent link for the current search.
508     */
509    public String getSearchPermanentLinkUrl() throws UnsupportedEncodingException {
510        // do not try to compute an URL if we don't have any CoreSession
511        if (documentManager == null) {
512            return null;
513        }
514
515        return generateSearchUrl(true);
516    }
517
518    /**
519     * @return the URL of the search tab with the search term defined.
520     */
521    public String getSearchTabUrl(String searchTerm) throws UnsupportedEncodingException {
522        // do not try to compute an URL if we don't have any CoreSession
523        if (documentManager == null) {
524            return null;
525        }
526        // Set the value of the searched term
527        this.searchTerm = searchTerm;
528
529        return generateSearchUrl(false);
530    }
531
532    /**
533     * Create the url to access the Search tab.
534     *
535     * @param withState If set to true, the state is added in the parameters.
536     */
537    protected String generateSearchUrl(boolean withState) throws UnsupportedEncodingException {
538        String currentContentViewName = getCurrentContentViewName();
539        DocumentModel currentDocument = navigationContext.getCurrentDocument();
540        DocumentView docView = computeDocumentView(currentDocument);
541        docView.setViewId("search");
542        docView.addParameter(CONTENT_VIEW_NAME_PARAMETER, currentContentViewName);
543        // Add the state if needed
544        if (withState) {
545            docView.addParameter(CONTENT_VIEW_STATE_PARAMETER, getJSONContentViewState());
546        }
547
548        DocumentViewCodecManager documentViewCodecManager = Framework.getService(DocumentViewCodecManager.class);
549        String url = documentViewCodecManager.getUrlFromDocumentView(SEARCH_CODEC, docView, true, BaseURL.getBaseURL());
550
551        return RestHelper.addCurrentConversationParameters(url);
552    }
553
554    protected DocumentView computeDocumentView(DocumentModel doc) {
555        return new DocumentViewImpl(new DocumentLocationImpl(documentManager.getRepositoryName(),
556                doc != null ? new PathRef(doc.getPathAsString()) : null));
557    }
558
559    /*
560     * ----- Simple Search -----
561     */
562    public String getSimpleSearchKeywords() {
563        return simpleSearchKeywords;
564    }
565
566    public void setSimpleSearchKeywords(String simpleSearchKeywords) {
567        this.simpleSearchKeywords = simpleSearchKeywords;
568    }
569
570    public void validateSimpleSearchKeywords(FacesContext context, UIComponent component, Object value) {
571        if (!(value instanceof String) || StringUtils.isEmpty(((String) value).trim())) {
572            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
573                    ComponentUtils.translate(context, "feedback.search.noKeywords"), null);
574            // also add global message
575            context.addMessage(null, message);
576            throw new ValidatorException(message);
577        }
578        String[] keywords = ((String) value).trim().split(" ");
579        for (String keyword : keywords) {
580            if (keyword.startsWith("*")) {
581                // Can't begin search with * character
582                FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
583                        ComponentUtils.translate(context, "feedback.search.star"), null);
584                // also add global message
585                context.addMessage(null, message);
586                throw new ValidatorException(message);
587            }
588        }
589    }
590
591    public String doSimpleSearch() {
592        setSearchMainTab(null);
593        currentContentViewName = SIMPLE_SEARCH_CONTENT_VIEW_NAME;
594        ContentView contentView = contentViewActions.getContentView(SIMPLE_SEARCH_CONTENT_VIEW_NAME);
595        DocumentModel searchDoc = contentView.getSearchDocumentModel();
596        searchDoc.setPropertyValue("defaults:ecm_fulltext", simpleSearchKeywords);
597        refreshAndRewind();
598        return "search";
599    }
600
601    /*
602     * ----- NXQL Search -----
603     */
604    public String getNxqlQuery() {
605        return nxqlQuery;
606    }
607
608    public void setNxqlQuery(String nxqlQuery) {
609        this.nxqlQuery = nxqlQuery;
610    }
611
612    public boolean isNxqlSearchSelected() {
613        return NXQL_SEARCH_CONTENT_VIEW_NAME.equals(currentContentViewName);
614    }
615
616    @Begin(id = "#{conversationIdGenerator.currentOrNewMainConversationId}", join = true)
617    public String loadPermanentLink(DocumentView docView) {
618        restHelper.initContextFromRestRequest(docView);
619        return "search";
620    }
621
622    @Observer(value = LOCAL_CONFIGURATION_CHANGED)
623    public void invalidateContentViewsName() {
624        clearSearch();
625        contentViewHeaders = null;
626        currentContentViewName = null;
627    }
628
629    @Observer(value = USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED)
630    public void invalidateContentViewsNameIfChanged() {
631        List<ContentViewHeader> temp = new ArrayList<>(
632                Framework.getService(SearchUIService.class).getContentViewHeaders(
633                        actionContextProvider.createActionContext(), navigationContext.getCurrentDocument()));
634        if (temp != null) {
635            if (!temp.equals(contentViewHeaders)) {
636                invalidateContentViewsName();
637            }
638            if (!temp.isEmpty()) {
639                String s = temp.get(0).getName();
640                if (s != null && !s.equals(currentContentViewName)) {
641                    invalidateContentViewsName();
642                }
643            }
644        }
645    }
646
647    /**
648     * Reset attributes.
649     */
650    @Observer(value = { EventNames.FLUSH_EVENT }, create = false)
651    @BypassInterceptors
652    public void resetOnFlush() {
653        contentViewHeaders = null;
654        currentSelectedSavedSearchId = null;
655        currentContentViewName = null;
656        nxqlQuery = DEFAULT_NXQL_QUERY;
657        simpleSearchKeywords = "";
658    }
659
660    public String getSearchTermParameter() {
661        return SEARCH_TERM_PARAMETER;
662    }
663
664    /**
665     * Triggers content view refresh/reset on saved search.
666     *
667     * @since 8.1
668     */
669    @Observer(value = { SEARCH_SAVED_EVENT })
670    public void onSearchSaved() {
671        contentViewActions.refreshOnSeamEvent(SEARCH_SAVED_EVENT);
672        contentViewActions.resetPageProviderOnSeamEvent(SEARCH_SAVED_EVENT);
673    }
674
675}