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