org.nuxeo.search.ui.seam.SearchUIActions.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.search.ui.seam.SearchUIActions.java

Source

/*
 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributors:
 *     Thomas Roger
 */

package org.nuxeo.search.ui.seam;

import static org.apache.commons.lang.StringUtils.isNotBlank;
import static org.apache.commons.logging.LogFactory.getLog;
import static org.jboss.seam.ScopeType.CONVERSATION;
import static org.jboss.seam.annotations.Install.FRAMEWORK;
import static org.nuxeo.ecm.webapp.helpers.EventNames.LOCAL_CONFIGURATION_CHANGED;
import static org.nuxeo.ecm.webapp.helpers.EventNames.USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;
import javax.faces.model.SelectItemGroup;
import javax.faces.validator.ValidatorException;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.jboss.seam.annotations.Begin;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.core.Events;
import org.jboss.seam.faces.FacesMessages;
import org.jboss.seam.international.StatusMessage;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.IdRef;
import org.nuxeo.ecm.core.api.PathRef;
import org.nuxeo.ecm.core.api.impl.DocumentLocationImpl;
import org.nuxeo.ecm.platform.contentview.jsf.ContentView;
import org.nuxeo.ecm.platform.contentview.jsf.ContentViewHeader;
import org.nuxeo.ecm.platform.contentview.jsf.ContentViewService;
import org.nuxeo.ecm.platform.contentview.jsf.ContentViewState;
import org.nuxeo.ecm.platform.contentview.jsf.ContentViewStateImpl;
import org.nuxeo.ecm.platform.contentview.json.JSONContentViewState;
import org.nuxeo.ecm.platform.contentview.seam.ContentViewActions;
import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
import org.nuxeo.ecm.platform.ui.web.api.WebActions;
import org.nuxeo.ecm.platform.ui.web.rest.RestHelper;
import org.nuxeo.ecm.platform.ui.web.util.BaseURL;
import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils;
import org.nuxeo.ecm.platform.url.DocumentViewImpl;
import org.nuxeo.ecm.platform.url.api.DocumentView;
import org.nuxeo.ecm.platform.url.api.DocumentViewCodecManager;
import org.nuxeo.ecm.webapp.action.ActionContextProvider;
import org.nuxeo.ecm.webapp.documentsLists.DocumentsListsManager;
import org.nuxeo.ecm.webapp.helpers.EventNames;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.search.ui.SearchUIService;

/**
 * Seam bean handling Search main tab actions.
 *
 * @since 6.0
 */
@Name("searchUIActions")
@Scope(CONVERSATION)
@Install(precedence = FRAMEWORK)
public class SearchUIActions implements Serializable {

    private static final long serialVersionUID = 1L;

    private static final Log log = getLog(SearchUIActions.class);

    public static final String SAVED_SEARCHES_LABEL = "label.saved.searches";

    public static final String SHARED_SEARCHES_LABEL = "label.shared.searches";

    public static final String SEARCH_FILTERS_LABEL = "label.search.filters";

    public static final String SEARCH_SAVED_LABEL = "label.search.saved";

    public static final String MAIN_TABS_SEARCH = "MAIN_TABS:search";

    public static final String SEARCH_VIEW_ID = "/search/search.xhtml";

    public static final String SEARCH_CODEC = "docpathsearch";

    public static final String SIMPLE_SEARCH_CONTENT_VIEW_NAME = "simple_search";

    public static final String NXQL_SEARCH_CONTENT_VIEW_NAME = "nxql_search";

    public static final String DEFAULT_NXQL_QUERY = "SELECT * FROM Document"
            + " WHERE ecm:mixinType != 'HiddenInNavigation'" + " AND ecm:isProxy = 0 AND ecm:isCheckedInVersion = 0"
            + " AND ecm:currentLifeCycleState != 'deleted'";

    public static final String CONTENT_VIEW_NAME_PARAMETER = "contentViewName";

    public static final String CURRENT_PAGE_PARAMETER = "currentPage";

    public static final String PAGE_SIZE_PARAMETER = "pageSize";

    public static final String CONTENT_VIEW_STATE_PARAMETER = "state";

    public static final String SEARCH_TERM_PARAMETER = "searchTerm";

    /**
     * Event name for search selection change, raised with selected corresponding content view name.
     *
     * @since 8.1
     */
    public static final String SEARCH_SELECTED_EVENT = "searchSelected";

    /**
     * Event name for search selection change, raised with saved document model.
     *
     * @since 8.1
     */
    public static final String SEARCH_SAVED_EVENT = "searchSaved";

    @In(create = true)
    protected transient NavigationContext navigationContext;

    @In(create = true, required = false)
    protected transient CoreSession documentManager;

    @In(create = true, required = false)
    protected transient ActionContextProvider actionContextProvider;

    @In(create = true)
    protected transient WebActions webActions;

    @In(create = true)
    protected RestHelper restHelper;

    @In(create = true)
    protected ContentViewActions contentViewActions;

    @In(create = true)
    protected ContentViewService contentViewService;

    @In(create = true)
    protected DocumentsListsManager documentsListsManager;

    @In(create = true, required = false)
    protected FacesMessages facesMessages;

    @In(create = true)
    protected Map<String, String> messages;

    protected String simpleSearchKeywords = "";

    protected String nxqlQuery = DEFAULT_NXQL_QUERY;

    protected List<ContentViewHeader> contentViewHeaders;

    protected String currentContentViewName;

    protected String currentSelectedSavedSearchId;

    protected String currentPage;

    protected String pageSize;

    protected String searchTerm;

    protected String savedSearchTitle;

    public String getSearchMainTab() {
        return MAIN_TABS_SEARCH;
    }

    public void setSearchMainTab(String tabs) {
        webActions.setCurrentTabIds(!StringUtils.isBlank(tabs) ? tabs : MAIN_TABS_SEARCH);
    }

    public String getSearchViewTitle() {
        if (currentSelectedSavedSearchId != null) {
            DocumentModel savedSearch = documentManager.getDocument(new IdRef(currentSelectedSavedSearchId));
            return savedSearch.getTitle();
        } else if (currentContentViewName != null) {
            ContentView cv = contentViewActions.getContentView(currentContentViewName);
            String title = cv.getTranslateTitle() ? messages.get(cv.getTitle()) : cv.getTitle();
            return isNotBlank(title) ? title : currentContentViewName;
        }
        return null;
    }

    /**
     * Returns true if the user is viewing SEARCH.
     */
    public boolean isOnSearchView() {
        if (FacesContext.getCurrentInstance() == null) {
            return false;
        }

        UIViewRoot viewRoot = FacesContext.getCurrentInstance().getViewRoot();
        if (viewRoot != null) {
            String viewId = viewRoot.getViewId();
            // FIXME find a better way to update the current document only
            // if we are on SEARCH
            if (SEARCH_VIEW_ID.equals(viewId)) {
                return true;
            }
        }
        return false;
    }

    public String getJSONContentViewState() throws UnsupportedEncodingException {
        ContentView contentView = contentViewActions.getContentView(currentContentViewName);
        ContentViewService contentViewService = Framework.getService(ContentViewService.class);
        ContentViewState state = contentViewService.saveContentView(contentView);
        return JSONContentViewState.toJSON(state, true);
    }

    public String getCurrentContentViewName() {
        if (currentContentViewName == null) {
            List<ContentViewHeader> contentViewHeaders = getContentViewHeaders();
            if (!contentViewHeaders.isEmpty()) {
                currentContentViewName = contentViewHeaders.get(0).getName();
            }
        }
        return currentContentViewName;
    }

    public void setCurrentContentViewName(String contentViewName) {
        this.currentContentViewName = contentViewName;
    }

    public String getCurrentSelectedSavedSearchId() {
        return currentSelectedSavedSearchId != null ? currentSelectedSavedSearchId : currentContentViewName;
    }

    public void setCurrentSelectedSavedSearchId(String selectedSavedSearchId) throws UnsupportedEncodingException {
        resetCurrentContentViewWorkingList();

        for (ContentViewHeader contentViewHeader : contentViewHeaders) {
            if (contentViewHeader.getName().equals(selectedSavedSearchId)) {
                contentViewActions.reset(currentContentViewName);
                currentContentViewName = selectedSavedSearchId;
                Events.instance().raiseEvent(SEARCH_SELECTED_EVENT, currentContentViewName);
                currentSelectedSavedSearchId = null;
                return;
            }
        }
        DocumentModel savedSearch = documentManager.getDocument(new IdRef(selectedSavedSearchId));
        loadSavedSearch(savedSearch);
    }

    protected void resetCurrentContentViewWorkingList() {
        if (currentContentViewName != null) {
            ContentView contentView = contentViewActions.getContentView(currentContentViewName);
            if (contentView != null) {
                documentsListsManager.resetWorkingList(contentView.getSelectionListName());
            }
        }
    }

    public void loadSavedSearch(DocumentModel searchDocument) throws UnsupportedEncodingException {
        SearchUIService searchUIService = Framework.getService(SearchUIService.class);
        ContentViewState contentViewState = searchUIService.loadSearch(searchDocument);
        if (contentViewState != null) {
            ContentView contentView = contentViewActions.restoreContentView(contentViewState);
            currentContentViewName = contentView.getName();
            Events.instance().raiseEvent(SEARCH_SELECTED_EVENT, currentContentViewName);
        }
        currentSelectedSavedSearchId = searchDocument.getId();
    }

    public List<ContentViewHeader> getContentViewHeaders() {
        if (contentViewHeaders == null) {
            SearchUIService searchUIService = Framework.getService(SearchUIService.class);
            contentViewHeaders = searchUIService.getContentViewHeaders(actionContextProvider.createActionContext(),
                    navigationContext.getCurrentDocument());
        }
        return contentViewHeaders;
    }

    public void clearSearch() {
        if (currentContentViewName != null) {
            contentViewActions.reset(currentContentViewName);
            resetCurrentContentViewWorkingList();
        }
    }

    public void refreshAndRewind() {
        String contentViewName = getCurrentContentViewName();
        if (contentViewName != null) {
            contentViewActions.refreshAndRewind(contentViewName);
            resetCurrentContentViewWorkingList();
        }
    }

    public void refreshAndRewindAndResetAggregates() {
        contentViewActions.resetAggregates(getCurrentContentViewName());
        refreshAndRewind();
    }

    /*
     * ----- Load / Save searches -----
     */

    public List<SelectItem> getAllSavedSearchesSelectItems() {
        List<SelectItem> items = new ArrayList<>();

        // Add flagged content views
        SelectItemGroup flaggedGroup = new SelectItemGroup(messages.get(SEARCH_FILTERS_LABEL));
        List<ContentViewHeader> flaggedSavedSearches = getContentViewHeaders();
        List<SelectItem> flaggedSavedSearchesItems = convertCVToSelectItems(flaggedSavedSearches);
        flaggedGroup.setSelectItems(
                flaggedSavedSearchesItems.toArray(new SelectItem[flaggedSavedSearchesItems.size()]));
        items.add(flaggedGroup);

        // Add saved searches
        List<DocumentModel> userSavedSearches = getSavedSearches();
        if (!userSavedSearches.isEmpty()) {
            SelectItemGroup userGroup = new SelectItemGroup(messages.get(SAVED_SEARCHES_LABEL));

            List<SelectItem> userSavedSearchesItems = convertToSelectItems(userSavedSearches);
            userGroup.setSelectItems(userSavedSearchesItems.toArray(new SelectItem[userSavedSearchesItems.size()]));
            items.add(userGroup);
        }

        // Add shared searches
        List<DocumentModel> otherUsersSavedFacetedSearches = getSharedSearches();
        if (!otherUsersSavedFacetedSearches.isEmpty()) {
            List<SelectItem> otherUsersSavedSearchesItems = convertToSelectItems(otherUsersSavedFacetedSearches);
            SelectItemGroup allGroup = new SelectItemGroup(messages.get(SHARED_SEARCHES_LABEL));
            allGroup.setSelectItems(
                    otherUsersSavedSearchesItems.toArray(new SelectItem[otherUsersSavedSearchesItems.size()]));
            items.add(allGroup);
        }
        return items;
    }

    protected List<DocumentModel> getSavedSearches() {
        SearchUIService searchUIService = Framework.getService(SearchUIService.class);
        return searchUIService.getCurrentUserSavedSearches(documentManager);
    }

    protected List<DocumentModel> getSharedSearches() {
        SearchUIService searchUIService = Framework.getService(SearchUIService.class);
        return searchUIService.getSharedSavedSearches(documentManager);
    }

    protected List<SelectItem> convertToSelectItems(List<DocumentModel> docs) {
        List<SelectItem> items = new ArrayList<>();
        for (DocumentModel doc : docs) {
            items.add(new SelectItem(doc.getId(), doc.getTitle(), ""));
        }
        return items;
    }

    protected List<SelectItem> convertCVToSelectItems(List<ContentViewHeader> contentViewHeaders) {
        List<SelectItem> items = new ArrayList<>();
        for (ContentViewHeader contentViewHeader : contentViewHeaders) {
            items.add(new SelectItem(contentViewHeader.getName(), messages.get(contentViewHeader.getTitle()), ""));
        }
        return items;
    }

    public String getSavedSearchTitle() {
        return savedSearchTitle;
    }

    public void setSavedSearchTitle(String savedSearchTitle) {
        this.savedSearchTitle = savedSearchTitle;
    }

    public String saveSearch() {
        ContentView contentView = contentViewActions.getContentView(getCurrentContentViewName());
        if (contentView != null) {
            ContentViewState state = contentViewService.saveContentView(contentView);
            SearchUIService searchUIService = Framework.getService(SearchUIService.class);
            DocumentModel savedSearch = searchUIService.saveSearch(documentManager, state, savedSearchTitle);
            currentSelectedSavedSearchId = savedSearch.getId();

            Events.instance().raiseEvent(SEARCH_SAVED_EVENT, savedSearch);

            savedSearchTitle = null;
            facesMessages.add(StatusMessage.Severity.INFO, messages.get(SEARCH_SAVED_LABEL));
        }
        return null;
    }

    /**
     * Retsurns true if current search can be saved.
     * <p>
     * Returns false if current content view is waiting for a first execution.
     *
     * @since 7.4
     */
    public boolean getCanSaveSearch() {
        ContentView contentView = contentViewActions.getContentView(getCurrentContentViewName());
        if (contentView != null) {
            boolean res = !contentView.isWaitForExecution() || contentView.isExecuted();
            return res;
        }
        return false;
    }

    public void cancelSaveSearch() {
        savedSearchTitle = null;
    }

    /*
     * ----- Permanent links -----
     */

    public void setState(String state) throws UnsupportedEncodingException {
        if (isNotBlank(state)) {
            Long finalPageSize = null;
            if (!StringUtils.isBlank(pageSize)) {
                try {
                    finalPageSize = Long.valueOf(pageSize);
                } catch (NumberFormatException e) {
                    log.warn(String.format("Unable to parse '%s' parameter with value '%s'", PAGE_SIZE_PARAMETER,
                            pageSize));
                }
            }

            Long finalCurrentPage = null;
            if (!StringUtils.isBlank(currentPage)) {
                try {
                    finalCurrentPage = Long.valueOf(currentPage);
                } catch (NumberFormatException e) {
                    log.warn(String.format("Unable to parse '%s' parameter with value '%s'", CURRENT_PAGE_PARAMETER,
                            currentPage));
                }
            }

            String cvName = getCurrentContentViewName();
            List<ContentViewHeader> contentViewHeaders = getContentViewHeaders();
            if (cvName != null && contentViewHeaders != null) {
                boolean canRestore = false;
                for (ContentViewHeader contentViewHeader : getContentViewHeaders()) {
                    if (cvName.equals(contentViewHeader.getName())) {
                        canRestore = true;
                    }
                }

                if (canRestore) {
                    contentViewActions.restoreContentView(getCurrentContentViewName(), finalCurrentPage,
                            finalPageSize, null, state);
                } else {
                    invalidateContentViewsName();
                }
            }
        }
    }

    public String getCurrentPage() {
        return currentPage;
    }

    public void setCurrentPage(String currentPage) {
        this.currentPage = currentPage;
    }

    public String getPageSize() {
        return pageSize;
    }

    public void setPageSize(String pageSize) {
        this.pageSize = pageSize;
    }

    public void setSearchTerm(String searchTerm) throws UnsupportedEncodingException {
        // If the search term is not defined, we don't do the logic
        if (!StringUtils.isEmpty(searchTerm)) {
            // By default, the "simple_search" content view is used
            currentContentViewName = SIMPLE_SEARCH_CONTENT_VIEW_NAME;
            // Create a ContentViewState
            ContentView cv = contentViewService.getContentView(SIMPLE_SEARCH_CONTENT_VIEW_NAME);
            DocumentModel searchDocumentModel = cv.getSearchDocumentModel();
            // set the search term
            searchDocumentModel.setPropertyValue("default_search:ecm_fulltext", searchTerm);
            ContentViewState state = new ContentViewStateImpl();
            state.setSearchDocumentModel(searchDocumentModel);
            state.setContentViewName(getCurrentContentViewName());
            ContentView ccv = contentViewActions.restoreContentView(state);
            ccv.setExecuted(true);
        }
    }

    /**
     * Compute a permanent link for the current search.
     */
    public String getSearchPermanentLinkUrl() throws UnsupportedEncodingException {
        // do not try to compute an URL if we don't have any CoreSession
        if (documentManager == null) {
            return null;
        }

        return generateSearchUrl(true);
    }

    /**
     * @return the URL of the search tab with the search term defined.
     */
    public String getSearchTabUrl(String searchTerm) throws UnsupportedEncodingException {
        // do not try to compute an URL if we don't have any CoreSession
        if (documentManager == null) {
            return null;
        }
        // Set the value of the searched term
        this.searchTerm = searchTerm;

        return generateSearchUrl(false);
    }

    /**
     * Create the url to access the Search tab.
     *
     * @param withState If set to true, the state is added in the parameters.
     */
    protected String generateSearchUrl(boolean withState) throws UnsupportedEncodingException {
        String currentContentViewName = getCurrentContentViewName();
        DocumentModel currentDocument = navigationContext.getCurrentDocument();
        DocumentView docView = computeDocumentView(currentDocument);
        docView.setViewId("search");
        docView.addParameter(CONTENT_VIEW_NAME_PARAMETER, currentContentViewName);
        // Add the state if needed
        if (withState) {
            docView.addParameter(CONTENT_VIEW_STATE_PARAMETER, getJSONContentViewState());
        }

        DocumentViewCodecManager documentViewCodecManager = Framework.getService(DocumentViewCodecManager.class);
        String url = documentViewCodecManager.getUrlFromDocumentView(SEARCH_CODEC, docView, true,
                BaseURL.getBaseURL());

        return RestHelper.addCurrentConversationParameters(url);
    }

    protected DocumentView computeDocumentView(DocumentModel doc) {
        return new DocumentViewImpl(new DocumentLocationImpl(documentManager.getRepositoryName(),
                doc != null ? new PathRef(doc.getPathAsString()) : null));
    }

    /*
     * ----- Simple Search -----
     */
    public String getSimpleSearchKeywords() {
        return simpleSearchKeywords;
    }

    public void setSimpleSearchKeywords(String simpleSearchKeywords) {
        this.simpleSearchKeywords = simpleSearchKeywords;
    }

    public void validateSimpleSearchKeywords(FacesContext context, UIComponent component, Object value) {
        if (!(value instanceof String) || StringUtils.isEmpty(((String) value).trim())) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
                    ComponentUtils.translate(context, "feedback.search.noKeywords"), null);
            // also add global message
            context.addMessage(null, message);
            throw new ValidatorException(message);
        }
        String[] keywords = ((String) value).trim().split(" ");
        for (String keyword : keywords) {
            if (keyword.startsWith("*")) {
                // Can't begin search with * character
                FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
                        ComponentUtils.translate(context, "feedback.search.star"), null);
                // also add global message
                context.addMessage(null, message);
                throw new ValidatorException(message);
            }
        }
    }

    public String doSimpleSearch() {
        setSearchMainTab(null);
        currentContentViewName = SIMPLE_SEARCH_CONTENT_VIEW_NAME;
        ContentView contentView = contentViewActions.getContentView(SIMPLE_SEARCH_CONTENT_VIEW_NAME);
        DocumentModel searchDoc = contentView.getSearchDocumentModel();
        searchDoc.setPropertyValue("defaults:ecm_fulltext", simpleSearchKeywords);
        refreshAndRewind();
        return "search";
    }

    /*
     * ----- NXQL Search -----
     */
    public String getNxqlQuery() {
        return nxqlQuery;
    }

    public void setNxqlQuery(String nxqlQuery) {
        this.nxqlQuery = nxqlQuery;
    }

    public boolean isNxqlSearchSelected() {
        return NXQL_SEARCH_CONTENT_VIEW_NAME.equals(currentContentViewName);
    }

    @Begin(id = "#{conversationIdGenerator.currentOrNewMainConversationId}", join = true)
    public String loadPermanentLink(DocumentView docView) {
        restHelper.initContextFromRestRequest(docView);
        return "search";
    }

    @Observer(value = LOCAL_CONFIGURATION_CHANGED)
    public void invalidateContentViewsName() {
        clearSearch();
        contentViewHeaders = null;
        currentContentViewName = null;
    }

    @Observer(value = USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED)
    public void invalidateContentViewsNameIfChanged() {
        List<ContentViewHeader> temp = new ArrayList<>(
                Framework.getService(SearchUIService.class).getContentViewHeaders(
                        actionContextProvider.createActionContext(), navigationContext.getCurrentDocument()));
        if (temp != null) {
            if (!temp.equals(contentViewHeaders)) {
                invalidateContentViewsName();
            }
            if (!temp.isEmpty()) {
                String s = temp.get(0).getName();
                if (s != null && !s.equals(currentContentViewName)) {
                    invalidateContentViewsName();
                }
            }
        }
    }

    /**
     * Reset attributes.
     */
    @Observer(value = { EventNames.FLUSH_EVENT }, create = false)
    @BypassInterceptors
    public void resetOnFlush() {
        contentViewHeaders = null;
        currentSelectedSavedSearchId = null;
        currentContentViewName = null;
        nxqlQuery = DEFAULT_NXQL_QUERY;
        simpleSearchKeywords = "";
    }

    public String getSearchTermParameter() {
        return SEARCH_TERM_PARAMETER;
    }

    /**
     * Triggers content view refresh/reset on saved search.
     *
     * @since 8.1
     */
    @Observer(value = { SEARCH_SAVED_EVENT })
    public void onSearchSaved() {
        contentViewActions.refreshOnSeamEvent(SEARCH_SAVED_EVENT);
        contentViewActions.resetPageProviderOnSeamEvent(SEARCH_SAVED_EVENT);
    }

}