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