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 *     Nelson Silva
019 */
020
021package org.nuxeo.search.ui;
022
023import static org.nuxeo.search.ui.localconfiguration.Constants.SEARCH_CONFIGURATION_FACET;
024
025import java.io.Serializable;
026import java.util.ArrayList;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033import org.nuxeo.ecm.core.api.CoreSession;
034import org.nuxeo.ecm.core.api.DocumentModel;
035import org.nuxeo.ecm.core.api.DocumentModelFactory;
036import org.nuxeo.ecm.core.api.DocumentRef;
037import org.nuxeo.ecm.core.api.SortInfo;
038import org.nuxeo.ecm.core.api.localconfiguration.LocalConfigurationService;
039import org.nuxeo.ecm.core.api.pathsegment.PathSegmentService;
040import org.nuxeo.ecm.platform.actions.Action;
041import org.nuxeo.ecm.platform.actions.ActionContext;
042import org.nuxeo.ecm.platform.actions.ejb.ActionManager;
043import org.nuxeo.ecm.platform.contentview.jsf.ContentViewHeader;
044import org.nuxeo.ecm.platform.contentview.jsf.ContentViewService;
045import org.nuxeo.ecm.platform.contentview.jsf.ContentViewState;
046import org.nuxeo.ecm.platform.contentview.jsf.ContentViewStateImpl;
047import org.nuxeo.ecm.platform.query.api.PageProvider;
048import org.nuxeo.ecm.platform.query.api.PageProviderService;
049import org.nuxeo.ecm.platform.userworkspace.api.UserWorkspaceService;
050import org.nuxeo.runtime.api.Framework;
051import org.nuxeo.search.ui.localconfiguration.SearchConfiguration;
052
053/**
054 * @since 6.0
055 */
056public class SearchUIServiceImpl implements SearchUIService {
057
058    private static Log log = LogFactory.getLog(SearchUIServiceImpl.class);
059
060    public static final String SEARCH_CONTENT_VIEWS_CATEGORY = "SEARCH_CONTENT_VIEWS";
061
062    public static final String CONTENT_VIEW_NAME_PROPERTY = "contentViewName";
063
064    public static final String SAVED_SEARCHES_PROVIDER_NAME = "SAVED_SEARCHES";
065
066    public static final String SHARED_SEARCHES_PROVIDER_NAME = "SHARED_SAVED_SEARCHES";
067
068    public static final String CONTENT_VIEW_DISPLAY_FACET = "ContentViewDisplay";
069
070    @Override
071    public List<ContentViewHeader> getContentViewHeaders(ActionContext actionContext) {
072        return getContentViewHeaders(actionContext, null);
073    }
074
075    @Override
076    public List<ContentViewHeader> getContentViewHeaders(ActionContext actionContext, DocumentModel doc) {
077        ActionManager actionService = Framework.getService(ActionManager.class);
078        List<Action> actions = actionService.getActions(SEARCH_CONTENT_VIEWS_CATEGORY, actionContext);
079
080        List<String> contentViewNames = new ArrayList<>();
081        for (Action action : actions) {
082            String contentViewName = (String) action.getProperties().get(CONTENT_VIEW_NAME_PROPERTY);
083            if (contentViewName != null) {
084                contentViewNames.add(contentViewName);
085            }
086        }
087        contentViewNames = filterContentViewNames(contentViewNames, doc);
088
089        ContentViewService contentViewService = Framework.getService(ContentViewService.class);
090        List<ContentViewHeader> contentViewHeaders = new ArrayList<>();
091        for (String contentViewName : contentViewNames) {
092            ContentViewHeader contentViewHeader = contentViewService.getContentViewHeader(contentViewName);
093            if (contentViewHeader != null) {
094                contentViewHeaders.add(contentViewHeader);
095            }
096        }
097        return contentViewHeaders;
098    }
099
100    /**
101     * Returns the filtered content view names based on the local configuration if any.
102     */
103    protected List<String> filterContentViewNames(List<String> contentViewNames, DocumentModel currentDoc) {
104        SearchConfiguration searchConfiguration = getSearchConfiguration(currentDoc);
105        return searchConfiguration == null ? contentViewNames
106                : searchConfiguration.filterAllowedContentViewNames(contentViewNames);
107    }
108
109    protected SearchConfiguration getSearchConfiguration(DocumentModel currentDoc) {
110        LocalConfigurationService localConfigurationService = Framework.getService(LocalConfigurationService.class);
111        return localConfigurationService.getConfiguration(SearchConfiguration.class, SEARCH_CONFIGURATION_FACET,
112                currentDoc);
113    }
114
115    public DocumentModel saveSearch(CoreSession session, ContentViewState searchContentViewState, String title) {
116        UserWorkspaceService userWorkspaceService = Framework.getService(UserWorkspaceService.class);
117        DocumentModel uws = userWorkspaceService.getCurrentUserPersonalWorkspace(session, null);
118
119        DocumentModel searchDoc = searchContentViewState.getSearchDocumentModel();
120        DocumentRef ref = searchDoc.getRef();
121        if (ref != null && session.exists(ref)) {
122            // already a saved search, init a new doc
123            DocumentModel bareDoc = DocumentModelFactory.createDocumentModel(searchDoc.getType());
124            bareDoc.copyContent(searchDoc);
125            searchDoc = bareDoc;
126        }
127        searchDoc.setPropertyValue("dc:title", title);
128
129        if (searchDoc.hasFacet(CONTENT_VIEW_DISPLAY_FACET)) {
130            searchDoc.setPropertyValue("cvd:contentViewName", searchContentViewState.getContentViewName());
131            searchDoc.setPropertyValue("saved:providerName", searchContentViewState.getPageProviderName());
132            searchDoc.setPropertyValue("saved:pageSize", searchContentViewState.getPageSize());
133            searchContentViewState.getPageSize();
134            List<SortInfo> sortInfos = searchContentViewState.getSortInfos();
135            if (sortInfos != null) {
136                ArrayList<Map<String, Serializable>> list = new ArrayList<>();
137                String sortBy = "", sortOrder = "";
138                for (SortInfo sortInfo : sortInfos) {
139                    if (!sortBy.isEmpty()) {
140                        sortBy += ",";
141                        sortOrder += ",";
142                    }
143                    sortBy += sortInfo.getSortColumn();
144                    sortOrder += sortInfo.getSortAscending() ? "ASC" : "DESC";
145                    list.add(SortInfo.asMap(sortInfo));
146                }
147                searchDoc.setPropertyValue("cvd:sortInfos", list);
148                searchDoc.setPropertyValue("saved:sortBy", sortBy);
149                searchDoc.setPropertyValue("saved:sortOrder", sortOrder);
150            }
151            searchDoc.setPropertyValue("cvd:selectedLayoutColumns",
152                    (Serializable) searchContentViewState.getResultColumns());
153        } else {
154            log.warn(String.format("Search document type %s is missing %s facet", searchDoc.getType(),
155                    CONTENT_VIEW_DISPLAY_FACET));
156        }
157
158        PathSegmentService pathService = Framework.getService(PathSegmentService.class);
159        searchDoc.setPathInfo(uws.getPathAsString(), pathService.generatePathSegment(searchDoc));
160        searchDoc = session.createDocument(searchDoc);
161        session.save();
162
163        return searchDoc;
164    }
165
166    public List<DocumentModel> getCurrentUserSavedSearches(CoreSession session) {
167        return getDocuments(SAVED_SEARCHES_PROVIDER_NAME, session, session.getPrincipal().getName());
168    }
169
170    @SuppressWarnings("unchecked")
171    protected List<DocumentModel> getDocuments(String pageProviderName, CoreSession session, Object... parameters) {
172        PageProviderService pageProviderService = Framework.getService(PageProviderService.class);
173        Map<String, Serializable> properties = new HashMap<String, Serializable>();
174        properties.put("coreSession", (Serializable) session);
175        return ((PageProvider<DocumentModel>) pageProviderService.getPageProvider(pageProviderName, null, null, null,
176                properties, parameters)).getCurrentPage();
177
178    }
179
180    public List<DocumentModel> getSharedSavedSearches(CoreSession session) {
181        return getDocuments(SHARED_SEARCHES_PROVIDER_NAME, session, session.getPrincipal().getName());
182    }
183
184    @Override
185    @SuppressWarnings("unchecked")
186    public ContentViewState loadSearch(DocumentModel savedSearch) {
187        if (!savedSearch.hasFacet(CONTENT_VIEW_DISPLAY_FACET)) {
188            log.warn(String.format("Search document type %s is missing %s facet", savedSearch.getType(),
189                    CONTENT_VIEW_DISPLAY_FACET));
190            return null;
191        }
192        ContentViewState state = new ContentViewStateImpl();
193        state.setContentViewName((String) savedSearch.getPropertyValue("cvd:contentViewName"));
194        state.setSearchDocumentModel(savedSearch);
195        state.setSortInfos(getSortInfos(savedSearch));
196        state.setResultColumns((List<String>) savedSearch.getPropertyValue("cvd:selectedLayoutColumns"));
197        return state;
198    }
199
200    @SuppressWarnings("unchecked")
201    List<SortInfo> getSortInfos(DocumentModel savedSearch) {
202        List<Map<String, Serializable>> list = (List<Map<String, Serializable>>) savedSearch.getPropertyValue(
203                "cvd:sortInfos");
204        List<SortInfo> sortInfos = new ArrayList<>();
205        for (Map<String, Serializable> info : list) {
206            sortInfos.add(SortInfo.asSortInfo(info));
207        }
208        return sortInfos;
209    }
210}