001/*
002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     <a href="mailto:grenard@nuxeo.com">Guillaume Renard</a>
016 */
017package org.nuxeo.ecm.platform.tag.web;
018
019import static org.jboss.seam.ScopeType.EVENT;
020
021import java.io.Serializable;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.Map.Entry;
028
029import net.sf.json.JSONArray;
030import net.sf.json.JSONObject;
031
032import org.apache.commons.lang.StringUtils;
033import org.jboss.seam.Component;
034import org.jboss.seam.annotations.Factory;
035import org.jboss.seam.annotations.In;
036import org.jboss.seam.annotations.Name;
037import org.jboss.seam.annotations.Scope;
038import org.jboss.seam.contexts.Contexts;
039import org.jboss.seam.faces.FacesMessages;
040import org.jboss.seam.international.StatusMessage;
041import org.nuxeo.ecm.core.api.CoreSession;
042import org.nuxeo.ecm.core.api.DocumentModel;
043import org.nuxeo.ecm.core.api.DocumentRef;
044import org.nuxeo.ecm.core.api.IdRef;
045import org.nuxeo.ecm.platform.tag.Tag;
046import org.nuxeo.ecm.platform.tag.TagService;
047import org.nuxeo.ecm.platform.ui.select2.common.Select2Common;
048import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
049import org.nuxeo.runtime.api.Framework;
050
051/**
052 * Helper component for tagging widget relying on select2.
053 *
054 * @since 6.0
055 */
056@Name("tagSelect2Support")
057@Scope(EVENT)
058public class TagSelect2Support {
059
060    @In(create = true)
061    protected NavigationContext navigationContext;
062
063    @In(create = true, required = false)
064    protected transient CoreSession documentManager;
065
066    @In(create = true, required = false)
067    protected transient FacesMessages facesMessages;
068
069    @In(create = true)
070    protected Map<String, String> messages;
071
072    @In(create = true, value = "currentDocumentTags")
073    protected List<Tag> currentDocumentTags;
074
075    protected String label;
076
077    @Factory(value = "resolveDocumentTags", scope = EVENT)
078    public String resolveDocumentTags() {
079        if (currentDocumentTags == null || currentDocumentTags.isEmpty()) {
080            return "[]";
081        } else {
082            JSONArray result = new JSONArray();
083            for (Tag tag : currentDocumentTags) {
084                JSONObject obj = new JSONObject();
085                obj.element(Select2Common.ID, tag.getLabel());
086                obj.element(Select2Common.LABEL, tag.getLabel());
087                result.add(obj);
088            }
089            return result.toString();
090        }
091    }
092
093    public String resolveTags(final List<String> list) {
094        return Select2Common.resolveDefaultEntries(list);
095    }
096
097    public String resolveTags(final String[] array) {
098        if (array == null || array.length == 0) {
099            return Select2Common.resolveDefaultEntries(null);
100        }
101        return Select2Common.resolveDefaultEntries(Arrays.asList(array));
102    }
103
104    @Factory(value = "documentTagIds", scope = EVENT)
105    public List<String> getDocumentTagStrings() {
106        if (currentDocumentTags == null || currentDocumentTags.isEmpty()) {
107            return null;
108        } else {
109            List<String> result = new ArrayList<String>();
110            for (Tag tag : currentDocumentTags) {
111                result.add(tag.getLabel());
112            }
113            return result;
114        }
115    }
116
117    /**
118     * Performs the tagging on the current document.
119     */
120    public String addTagging() {
121        String messageKey;
122        if (StringUtils.isBlank(label)) {
123            messageKey = "message.add.new.tagging.not.empty";
124        } else {
125            DocumentModel currentDocument = navigationContext.getCurrentDocument();
126            String docId = currentDocument.getId();
127
128            TagService tagService = getTagService();
129            tagService.tag(documentManager, docId, label, null);
130            if (currentDocument.isVersion()) {
131                DocumentModel liveDocument = documentManager.getSourceDocument(currentDocument.getRef());
132                if (!liveDocument.isCheckedOut()) {
133                    tagService.tag(documentManager, liveDocument.getId(), label, null);
134                }
135            } else if (!currentDocument.isCheckedOut()) {
136                DocumentRef ref = documentManager.getBaseVersion(currentDocument.getRef());
137                if (ref instanceof IdRef) {
138                    tagService.tag(documentManager, ref.toString(), label, null);
139                }
140            }
141            messageKey = "message.add.new.tagging";
142            // force invalidation
143            Contexts.getEventContext().remove("resolveDocumentTags");
144        }
145        facesMessages.add(StatusMessage.Severity.INFO, messages.get(messageKey), label);
146        reset();
147        return null;
148    }
149
150    public String removeTagging() {
151        DocumentModel currentDocument = navigationContext.getCurrentDocument();
152        String docId = currentDocument.getId();
153
154        TagService tagService = getTagService();
155        tagService.untag(documentManager, docId, label, null);
156
157        if (currentDocument.isVersion()) {
158            DocumentModel liveDocument = documentManager.getSourceDocument(currentDocument.getRef());
159            if (!liveDocument.isCheckedOut()) {
160                tagService.untag(documentManager, liveDocument.getId(), label, null);
161            }
162        } else if (!currentDocument.isCheckedOut()) {
163            DocumentRef ref = documentManager.getBaseVersion(currentDocument.getRef());
164            if (ref instanceof IdRef) {
165                tagService.untag(documentManager, ref.toString(), label, null);
166            }
167        }
168        // force invalidation
169        Contexts.getEventContext().remove("currentDocumentTags");
170        facesMessages.add(StatusMessage.Severity.INFO, messages.get("message.remove.tagging"), label);
171        reset();
172        return null;
173    }
174
175    protected void reset() {
176        label = null;
177    }
178
179    protected TagService getTagService() {
180        TagService tagService = Framework.getService(TagService.class);
181        return tagService.isEnabled() ? tagService : null;
182    }
183
184    public String encodeParameters(final Map<String, Serializable> widgetProperties) {
185        return encodeCommonParameters(widgetProperties).toString();
186    }
187
188    public String encodeParametersForCurrentDocument(final Map<String, Serializable> widgetProperties) {
189        Map<String, String> parameters = new HashMap<String, String>();
190        parameters.put("onAddEntryHandler", "addTagHandler");
191        parameters.put("onRemoveEntryHandler", "removeTagHandler");
192        parameters.put("containerCssClass", "s2tagContainerCssClass");
193        parameters.put("dropdownCssClass", "s2tagDropdownCssClass");
194        parameters.put("createSearchChoice", "createNewTag");
195        if (widgetProperties.containsKey("canSelectNewTag")
196                && !Boolean.parseBoolean((String) widgetProperties.get("canSelectNewTag"))) {
197            parameters.remove("createSearchChoice");
198        }
199        return encodeCommonParameters(widgetProperties, parameters).toString();
200    }
201
202    protected JSONObject encodeCommonParameters(final Map<String, Serializable> widgetProperties) {
203        return encodeCommonParameters(widgetProperties, null);
204    }
205
206    protected JSONObject encodeCommonParameters(final Map<String, Serializable> widgetProperties,
207            final Map<String, String> additionalParameters) {
208        JSONObject obj = new JSONObject();
209        obj.put("multiple", "true");
210        obj.put(Select2Common.MIN_CHARS, "1");
211        obj.put(Select2Common.READ_ONLY_PARAM, "false");
212        if (widgetProperties.containsKey("canSelectNewTag")
213                && Boolean.parseBoolean((String) widgetProperties.get("canSelectNewTag"))) {
214            obj.put("createSearchChoice", "createNewTag");
215        }
216        obj.put(Select2Common.OPERATION_ID, "Tag.Suggestion");
217        obj.put(Select2Common.WIDTH, "300px");
218        obj.put(Select2Common.SELECTION_FORMATTER, "formatSelectedTags");
219        obj.put(Select2Common.SUGGESTION_FORMATTER, "formatSuggestedTags");
220        JSONArray tokenSeparator = new JSONArray();
221        tokenSeparator.add(",");
222        tokenSeparator.add(" ");
223        obj.put("tokenSeparators", tokenSeparator);
224        if (additionalParameters != null) {
225            for (Entry<String, String> entry : additionalParameters.entrySet()) {
226                obj.put(entry.getKey(), entry.getValue().toString());
227            }
228        }
229        for (Entry<String, Serializable> entry : widgetProperties.entrySet()) {
230            obj.put(entry.getKey(), entry.getValue().toString());
231        }
232        return obj;
233    }
234
235    /**
236     * @since 7.1
237     */
238    public void listDocumentsForTag() {
239        final TagActionsBean tagActionsBean = (TagActionsBean) Component.getInstance(TagActionsBean.class);
240        tagActionsBean.setListLabel(label);
241    }
242
243    public String getLabel() {
244        return label;
245    }
246
247    public void setLabel(String label) {
248        this.label = label;
249    }
250}