org.nuxeo.ecm.platform.faceted.search.jsf.FacetedSearchActions.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.platform.faceted.search.jsf.FacetedSearchActions.java

Source

/*
 * (C) Copyright 2010 Nuxeo SAS (http://nuxeo.com/) and contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * Contributors:
 *     Nuxeo - initial API and implementation
 */

package org.nuxeo.ecm.platform.faceted.search.jsf;

import static org.jboss.seam.ScopeType.CONVERSATION;
import static org.jboss.seam.ScopeType.EVENT;
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.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.faces.model.SelectItem;
import javax.faces.model.SelectItemGroup;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Factory;
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.core.Events;
import org.jboss.seam.faces.FacesMessages;
import org.jboss.seam.international.StatusMessage;
import org.json.JSONException;
import org.nuxeo.common.utils.Path;
import org.nuxeo.ecm.core.api.ClientException;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DataModel;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentRef;
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.core.api.impl.DocumentModelImpl;
import org.nuxeo.ecm.core.api.pathsegment.PathSegmentService;
import org.nuxeo.ecm.core.schema.SchemaManager;
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.seam.ContentViewActions;
import org.nuxeo.ecm.platform.faceted.search.api.Constants;
import org.nuxeo.ecm.platform.faceted.search.api.service.Configuration;
import org.nuxeo.ecm.platform.faceted.search.api.service.FacetedSearchService;
import org.nuxeo.ecm.platform.faceted.search.api.util.JSONMetadataExporter;
import org.nuxeo.ecm.platform.faceted.search.api.util.JSONMetadataHelper;
import org.nuxeo.ecm.platform.forms.layout.io.Base64;
import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
import org.nuxeo.ecm.platform.ui.web.util.BaseURL;
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.platform.userworkspace.api.UserWorkspaceService;
import org.nuxeo.ecm.webapp.helpers.EventNames;
import org.nuxeo.ecm.webapp.helpers.ResourcesAccessor;
import org.nuxeo.runtime.api.Framework;

/**
 * Handles faceted search related web actions.
 *
 * @author <a href="mailto:troger@nuxeo.com">Thomas Roger</a>
 * @since 5.4
 */
@Name("facetedSearchActions")
@Scope(CONVERSATION)
@Install(precedence = FRAMEWORK)
public class FacetedSearchActions implements Serializable {

    private static final long serialVersionUID = 1L;

    public static final String DUBLINCORE_SCHEMA = "dublincore";

    public static final String NONE_VALUE = "none";

    public static final String NONE_LABEL = "label.none";

    public static final String USER_SAVED_SEARCHES_LABEL = "label.user.saved.searches";

    public static final String ALL_SAVED_SEARCHES_LABEL = "label.all.saved.searches";

    public static final String FLAGGED_SAVED_SEARCHES_LABEL = "label.flagged.saved.searches";

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

    public static final String FACETED_SEARCH_CODEC = "facetedSearch";

    public static final String CONTENT_VIEW_NAME_PARAMETER = "contentViewName";

    public static final String FILTER_VALUES_PARAMETER = "values";

    public static final String ENCODED_VALUES_ENCODING = "UTF-8";

    @In(create = true)
    protected ContentViewActions contentViewActions;

    @In(create = true)
    protected transient ContentViewService contentViewService;

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

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

    @In(create = true)
    protected ResourcesAccessor resourcesAccessor;

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

    protected List<String> contentViewNames;

    protected Set<ContentViewHeader> contentViewHeaders;

    protected String currentContentViewName;

    protected String currentSelectedSavedSearchId;

    protected String savedSearchTitle;

    @Factory(value = "facetedSearchCurrentContentViewName", scope = EVENT)
    public String getCurrentContentViewName() throws ClientException {
        if (currentContentViewName == null) {
            List<String> contentViewNames = getContentViewNames();
            if (!contentViewNames.isEmpty()) {
                currentContentViewName = contentViewNames.get(0);
                currentSelectedSavedSearchId = currentContentViewName;
            }
        }
        return currentContentViewName;
    }

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

    public List<String> getContentViewNames() throws ClientException {
        if (contentViewNames == null) {
            contentViewNames = new ArrayList<String>(Framework.getLocalService(FacetedSearchService.class)
                    .getContentViewNames(navigationContext.getCurrentDocument()));
        }
        return contentViewNames;
    }

    public Set<ContentViewHeader> getContentViewHeaders() throws ClientException {
        if (contentViewHeaders == null) {
            contentViewHeaders = new LinkedHashSet<ContentViewHeader>();
            for (String name : getContentViewNames()) {
                ContentViewHeader header = contentViewService.getContentViewHeader(name);
                if (header != null) {
                    contentViewHeaders.add(header);
                }
            }
        }
        return contentViewHeaders;
    }

    public void clearSearch() {
        contentViewActions.reset(currentContentViewName);
        this.currentSelectedSavedSearchId = null;
    }

    /*
     * ----- Retrieving user and all saved searches -----
     */

    public List<DocumentModel> getCurrentUserSavedSearches() throws ClientException {
        return Framework.getLocalService(FacetedSearchService.class).getCurrentUserSavedSearches(documentManager);
    }

    public List<DocumentModel> getOtherUsersSavedSearches() throws ClientException {
        return Framework.getLocalService(FacetedSearchService.class).getOtherUsersSavedSearches(documentManager);
    }

    @Factory(value = "allSavedSearchesSelectItems", scope = ScopeType.EVENT)
    public List<SelectItem> getAllSavedSearchesSelectItems() throws ClientException {
        List<SelectItem> items = new ArrayList<SelectItem>();
        // Add none label
        items.add(new SelectItem(NONE_VALUE, resourcesAccessor.getMessages().get(NONE_LABEL)));
        // Add current user searches
        SelectItemGroup userGroup = new SelectItemGroup(
                resourcesAccessor.getMessages().get(USER_SAVED_SEARCHES_LABEL));
        List<DocumentModel> userSavedSearches = getCurrentUserSavedSearches();
        List<SelectItem> userSavedSearchesItems = convertToSelectItems(userSavedSearches);
        userGroup.setSelectItems(userSavedSearchesItems.toArray(new SelectItem[userSavedSearchesItems.size()]));
        items.add(userGroup);
        // Add shared searches
        List<DocumentModel> otherUsersSavedFacetedSearches = getOtherUsersSavedSearches();
        List<SelectItem> otherUsersSavedSearchesItems = convertToSelectItems(otherUsersSavedFacetedSearches);
        SelectItemGroup allGroup = new SelectItemGroup(
                resourcesAccessor.getMessages().get(ALL_SAVED_SEARCHES_LABEL));
        allGroup.setSelectItems(
                otherUsersSavedSearchesItems.toArray(new SelectItem[otherUsersSavedSearchesItems.size()]));
        items.add(allGroup);
        SelectItemGroup flaggedGroup = new SelectItemGroup(
                resourcesAccessor.getMessages().get(FLAGGED_SAVED_SEARCHES_LABEL));
        // Add faceted flagged content views
        Set<ContentViewHeader> flaggedSavedSearches = getContentViewHeaders();
        List<SelectItem> flaggedSavedSearchesItems = convertCVToSelectItems(flaggedSavedSearches);
        flaggedGroup.setSelectItems(
                flaggedSavedSearchesItems.toArray(new SelectItem[flaggedSavedSearchesItems.size()]));
        items.add(flaggedGroup);
        return items;
    }

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

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

    /*
     * ----- Saving and loading a search -----
     */

    public void setCurrentSelectedSavedSearchId(String savedSearchId) throws ClientException {
        this.currentSelectedSavedSearchId = savedSearchId;

    }

    public String getCurrentSelectedSavedSearchId() {
        return currentSelectedSavedSearchId;
    }

    public String loadSelectedSavedSearch(String jsfView) throws ClientException {
        loadSelectedSavedSearch();
        return jsfView;
    }

    public void loadSelectedSavedSearch() throws ClientException {
        if (currentSelectedSavedSearchId == null || currentSelectedSavedSearchId.isEmpty()
                || NONE_VALUE.equals(currentSelectedSavedSearchId)) {
            contentViewActions.reset(currentContentViewName);
        } else {
            loadSavedSearch(currentSelectedSavedSearchId);
        }
    }

    public void loadSavedSearch(String savedSearchId) throws ClientException {
        String contentViewName;
        DocumentModel savedSearch;
        // Check if the selected entry is a flagged content view (FACETED_SEARCH
        // flag)
        // Set the current contentViewName to this one
        if (contentViewNames.contains(savedSearchId)) {
            contentViewActions.reset(currentContentViewName);
            this.currentContentViewName = savedSearchId;
        } else {
            savedSearch = documentManager.getDocument(new IdRef(savedSearchId));
            contentViewName = (String) savedSearch
                    .getPropertyValue(Constants.FACETED_SEARCH_CONTENT_VIEW_NAME_PROPERTY);
            loadSavedSearch(contentViewName, savedSearch);
        }
    }

    public void loadSavedSearch(String contentViewName, DocumentModel searchDocument) throws ClientException {
        // Do not reuse the existing document as it can be modified and saved
        // again
        DocumentModel newSearchDocument = createDocumentModelFrom(searchDocument);
        ContentView contentView = contentViewActions.getContentView(contentViewName, newSearchDocument);
        if (contentView != null) {
            this.currentContentViewName = contentViewName;
        }
    }

    public String getSavedSearchTitle() {
        return savedSearchTitle;
    }

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

    public void saveSearch() throws ClientException {
        ContentView contentView = contentViewActions.getContentView(currentContentViewName);
        if (contentView != null) {
            DocumentModel savedSearch = Framework.getLocalService(FacetedSearchService.class)
                    .saveSearch(documentManager, contentView, savedSearchTitle);
            currentSelectedSavedSearchId = savedSearch.getId();
            savedSearchTitle = null;

            // Do not reuse the just saved document as it can be modified and
            // re-saved
            DocumentModel searchDocument = createDocumentModelFrom(savedSearch);
            contentView.setSearchDocumentModel(searchDocument);

            Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED, savedSearch);
            facesMessages.add(StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get(SEARCH_SAVED_LABEL));
        }
    }

    /**
     * Create a new {@code DocumentModel} with the same type as {@code sourceDoc}. Copy all the {@code DataModel}s from
     * {@code sourceDoc} to the newly created document, except the {@code dublincore} schema.
     */
    protected DocumentModel createDocumentModelFrom(DocumentModel sourceDoc) throws ClientException {
        DocumentModel doc = documentManager.createDocumentModel(sourceDoc.getType());
        for (String schema : sourceDoc.getDocumentType().getSchemaNames()) {
            // Copy everything except dublincore schema, required values will
            // be created again on the next save, if any
            if (!DUBLINCORE_SCHEMA.equals(schema)) {
                DataModel dm = sourceDoc.getDataModel(schema);
                SchemaManager mgr = Framework.getLocalService(SchemaManager.class);
                DataModel newDM = DocumentModelImpl.cloneDataModel(mgr.getSchema(dm.getSchema()), dm);
                doc.getDataModel(schema).setMap(newDM.getMap());
            }
        }
        return doc;
    }

    /*
     * ----- Permanent link generation and loading -----
     */

    protected String encodeValues(String values) throws UnsupportedEncodingException {
        String encodedValues = Base64.encodeBytes(values.getBytes(), Base64.GZIP | Base64.DONT_BREAK_LINES);
        encodedValues = URLEncoder.encode(encodedValues, ENCODED_VALUES_ENCODING);
        return encodedValues;
    }

    protected String decodeValues(String values) throws UnsupportedEncodingException {
        String decodedValues = URLDecoder.decode(values, ENCODED_VALUES_ENCODING);
        decodedValues = new String(Base64.decode(decodedValues));
        return decodedValues;
    }

    /**
     * Set the metadata of the SearchDocumentModel from an encoded JSON string.
     */
    public void setFilterValues(String filterValues)
            throws ClientException, JSONException, UnsupportedEncodingException {
        ContentView contentView = contentViewActions.getContentView(currentContentViewName);
        DocumentModel searchDocumentModel = contentView.getSearchDocumentModel();
        String decodedValues = decodeValues(filterValues);
        searchDocumentModel = JSONMetadataHelper.setPropertiesFromJson(searchDocumentModel, decodedValues);
        contentView.setSearchDocumentModel(searchDocumentModel);
    }

    /**
     * Compute a permanent link for the current search.
     */
    public String getPermanentLinkUrl() throws ClientException, UnsupportedEncodingException {
        DocumentView docView = new DocumentViewImpl(
                new DocumentLocationImpl(documentManager.getRepositoryName(), null));
        docView.addParameter(CONTENT_VIEW_NAME_PARAMETER, currentContentViewName);
        ContentView contentView = contentViewActions.getContentView(currentContentViewName);
        DocumentModel doc = contentView.getSearchDocumentModel();
        String values = getEncodedValuesFrom(doc);
        docView.addParameter(FILTER_VALUES_PARAMETER, values);
        DocumentViewCodecManager documentViewCodecManager = getDocumentViewCodecService();
        return documentViewCodecManager.getUrlFromDocumentView(FACETED_SEARCH_CODEC, docView, true,
                BaseURL.getBaseURL());
    }

    protected DocumentViewCodecManager getDocumentViewCodecService() throws ClientException {
        try {
            return Framework.getService(DocumentViewCodecManager.class);
        } catch (Exception e) {
            final String errMsg = "Could not retrieve the document view service. " + e.getMessage();
            throw new ClientException(errMsg, e);
        }
    }

    /**
     * Returns an encoded JSON string computed from the {@code doc} metadata.
     */
    protected String getEncodedValuesFrom(DocumentModel doc) throws ClientException, UnsupportedEncodingException {
        JSONMetadataExporter exporter = new JSONMetadataExporter();
        String values = exporter.run(doc).toString();
        return encodeValues(values);
    }

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

    /**
     * @throws ClientException
     * @since 5.8
     */
    @Observer(value = USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED)
    public void invalidateContentViewsNameIfChanged() throws ClientException {
        List<String> temp = new ArrayList<String>(Framework.getLocalService(FacetedSearchService.class)
                .getContentViewNames(navigationContext.getCurrentDocument()));

        if (temp != null) {
            if (!temp.equals(contentViewNames)) {
                invalidateContentViewsName();
            }
        }
    }

    /*
     * ----- Saved searches migration -----
     */

    public String getRootSavedSearchesTitle() {
        FacetedSearchService facetedSearchService = Framework.getLocalService(FacetedSearchService.class);
        Configuration configuration = facetedSearchService.getConfiguration();
        return configuration != null ? configuration.getRootSavedSearchesTitle() : null;
    }

    public boolean haveOldSavedSearches() throws ClientException {
        return !getOldSavedSearches().isEmpty();
    }

    protected List<DocumentModel> getOldSavedSearches() throws ClientException {
        UserWorkspaceService userWorkspaceService = Framework.getLocalService(UserWorkspaceService.class);
        FacetedSearchService facetedSearchService = Framework.getLocalService(FacetedSearchService.class);
        DocumentModel uws = userWorkspaceService.getCurrentUserPersonalWorkspace(documentManager, null);
        Configuration configuration = facetedSearchService.getConfiguration();
        String rootSavedSearchesTitle = configuration.getRootSavedSearchesTitle();

        PathSegmentService pathService = Framework.getLocalService(PathSegmentService.class);
        DocumentModel rootSavedSearches = documentManager.createDocumentModel(uws.getPathAsString(),
                rootSavedSearchesTitle, Constants.FACETED_SAVED_SEARCH_FOLDER);
        rootSavedSearches.setPathInfo(uws.getPathAsString(), pathService.generatePathSegment(rootSavedSearches));
        Path path = new Path(uws.getPathAsString()).append(pathService.generatePathSegment(rootSavedSearches));
        PathRef rootPathRef = new PathRef(path.toString());

        if (documentManager.exists(rootPathRef)) {
            DocumentModel rootDoc = documentManager.getDocument(rootPathRef);
            String query = String.format(
                    "SELECT * FROM Document WHERE ecm:mixinType = 'FacetedSearch' " + "AND ecm:parentId = '%s'",
                    rootDoc.getId());
            return documentManager.query(query);
        }
        return Collections.emptyList();
    }

    public void migrateOldSavedSearches() throws ClientException {
        UserWorkspaceService userWorkspaceService = Framework.getLocalService(UserWorkspaceService.class);
        DocumentModel uws = userWorkspaceService.getCurrentUserPersonalWorkspace(documentManager, null);

        List<DocumentModel> docs = getOldSavedSearches();
        if (!docs.isEmpty()) {
            documentManager.move(convertToDocumentRefs(docs), uws.getRef());
            facesMessages.addFromResourceBundle(StatusMessage.Severity.INFO,
                    "label.faceted.saved.searches.migrated", docs.size());
            contentViewActions.refreshOnSeamEvent("savedSearchesMigrated");
        }
    }

    protected List<DocumentRef> convertToDocumentRefs(List<DocumentModel> docs) {
        List<DocumentRef> refs = new ArrayList<DocumentRef>();
        for (DocumentModel doc : docs) {
            refs.add(doc.getRef());
        }
        return refs;
    }

}