001/*
002 * (C) Copyright 2010 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 *     Olivier Grisel
018 *
019 */
020package org.nuxeo.ecm.platform.suggestbox.jsf;
021
022import static org.jboss.seam.ScopeType.CONVERSATION;
023
024import java.io.Serializable;
025import java.util.Collections;
026import java.util.List;
027import java.util.Locale;
028import java.util.Map;
029
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.jboss.seam.annotations.In;
033import org.jboss.seam.annotations.Name;
034import org.jboss.seam.annotations.Scope;
035import org.jboss.seam.annotations.web.RequestParameter;
036import org.nuxeo.ecm.core.api.CoreSession;
037import org.nuxeo.ecm.core.api.DocumentModel;
038import org.nuxeo.ecm.platform.contentview.seam.ContentViewActions;
039import org.nuxeo.ecm.platform.suggestbox.service.Suggestion;
040import org.nuxeo.ecm.platform.suggestbox.service.SuggestionContext;
041import org.nuxeo.ecm.platform.suggestbox.service.SuggestionException;
042import org.nuxeo.ecm.platform.suggestbox.service.SuggestionService;
043import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
044import org.nuxeo.ecm.platform.ui.web.invalidations.AutomaticDocumentBasedInvalidation;
045import org.nuxeo.ecm.platform.ui.web.invalidations.DocumentContextBoundActionBean;
046import org.nuxeo.ecm.webapp.tree.nav.MultiNavTreeManager;
047import org.nuxeo.runtime.api.Framework;
048
049/**
050 * Back seam component for the top right search box using the suggestion service to help decode the user intent and
051 * minimize the number of clicks to find the relevant information.
052 */
053@Name("suggestboxActions")
054@Scope(CONVERSATION)
055@AutomaticDocumentBasedInvalidation
056public class SuggestboxActions extends DocumentContextBoundActionBean implements Serializable {
057
058    private static final Log log = LogFactory.getLog(SuggestboxActions.class);
059
060    private static final long serialVersionUID = 1L;
061
062    @In(create = true, required = false)
063    protected transient CoreSession documentManager;
064
065    @In(create = true)
066    protected transient NavigationContext navigationContext;
067
068    @In(create = true)
069    protected Map<String, String> messages;
070
071    @In(create = true)
072    protected Locale locale;
073
074    @In(create = true)
075    protected MultiNavTreeManager multiNavTreeManager;
076
077    /*
078     * @In(create = true) protected FacetedSearchActions facetedSearchActions;
079     */
080
081    @In(create = true)
082    protected ContentViewActions contentViewActions;
083
084    // keep suggestions in cache for maximum 10 seconds to avoid useless and
085    // costly re-computation of the suggestions by rich:suggestionbox at
086    // selection time
087    protected Cached<List<Suggestion>> cachedSuggestions = new Cached<List<Suggestion>>(10000);
088
089    protected String searchKeywords = "";
090
091    protected String suggesterGroup;
092
093    public String getSearchKeywords() {
094        return searchKeywords;
095    }
096
097    public void setSearchKeywords(String searchKeywords) {
098        this.searchKeywords = searchKeywords;
099    }
100
101    public String getSuggesterGroup() {
102        return suggesterGroup;
103    }
104
105    @RequestParameter
106    public void setSuggesterGroup(String suggesterGroup) {
107        this.suggesterGroup = suggesterGroup;
108    }
109
110    protected SuggestionContext getSuggestionContext() {
111        SuggestionContext ctx = new SuggestionContext(suggesterGroup, documentManager.getPrincipal()).withSession(
112                documentManager).withCurrentDocument(navigationContext.getCurrentDocument()).withLocale(locale).withMessages(
113                messages);
114        return ctx;
115    }
116
117    /**
118     * Callback for the ajax keypress event that triggers the generation of context sensitive action suggestions. The
119     * most specific actions (e.g. direct navigation to a document with matching titles) should be suggested in the
120     * first position and more generic (traditional free-text search for documents) last.
121     */
122    public List<Suggestion> getSuggestions(Object input) {
123        if (cachedSuggestions.hasExpired(input, locale)) {
124            SuggestionService service = Framework.getLocalService(SuggestionService.class);
125            SuggestionContext ctx = getSuggestionContext();
126            try {
127                List<Suggestion> suggestions = service.suggest(input.toString(), ctx);
128                cachedSuggestions.cache(suggestions, input, locale);
129            } catch (SuggestionException e) {
130                // log the exception rather than trying to display it since
131                // this
132                // method is called by ajax events when typing in the
133                // searchbox.
134                log.error(e, e);
135                return Collections.emptyList();
136            }
137        }
138        return cachedSuggestions.value;
139    }
140
141    @Override
142    protected void resetBeanCache(DocumentModel newCurrentDocumentModel) {
143        cachedSuggestions.expire();
144    }
145
146}