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