/*
* (C) Copyright 2009 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Radu Darlea
* Bogdan Tatar
* Florent Guillaume
*/
package org.nuxeo.ecm.platform.tag.web;
import static org.jboss.seam.ScopeType.APPLICATION;
import static org.jboss.seam.ScopeType.CONVERSATION;
import static org.jboss.seam.ScopeType.EVENT;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.faces.event.ActionEvent;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.seam.annotations.Factory;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.annotations.web.RequestParameter;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.faces.FacesMessages;
import org.jboss.seam.international.StatusMessage;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentModelList;
import org.nuxeo.ecm.core.api.DocumentNotFoundException;
import org.nuxeo.ecm.core.api.DocumentRef;
import org.nuxeo.ecm.core.api.DocumentSecurityException;
import org.nuxeo.ecm.core.api.IdRef;
import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
import org.nuxeo.ecm.platform.tag.Tag;
import org.nuxeo.ecm.platform.tag.TagService;
import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
import org.nuxeo.ecm.webapp.helpers.EventNames;
import org.nuxeo.ecm.webapp.helpers.ResourcesAccessor;
import org.nuxeo.runtime.api.Framework;
/**
* This Seam bean provides support for tagging related actions which can be made on the current document.
*/
@Name("tagActions")
@Scope(CONVERSATION)
public class TagActionsBean implements Serializable {
private static final long serialVersionUID = 1L;
private static final Log log = LogFactory.getLog(TagActionsBean.class);
public static final String TAG_SEARCH_RESULT_PAGE = "tag_search_results";
public static final String SELECTION_EDITED = "selectionEdited";
public static final String DOCUMENTS_IMPORTED = "documentImported";
@In(create = true, required = false)
protected transient CoreSession documentManager;
@In(create = true)
protected NavigationContext navigationContext;
@In(create = true, required = false)
protected transient FacesMessages facesMessages;
@In(create = true)
protected transient ResourcesAccessor resourcesAccessor;
protected String listLabel;
// protected LRUCachingMap<String, Boolean> tagModifyCheckCache = new
// LRUCachingMap<String, Boolean>(
// 1);
/**
* Keeps the tagging information that will be performed on the current document document.
*/
private String tagLabel;
/**
* Controls the presence of the tagging text field in UI.
*/
private boolean addTag;
@RequestParameter
protected Boolean canSelectNewTag;
@Factory(value = "tagServiceEnabled", scope = APPLICATION)
public boolean isTagServiceEnabled() {
return getTagService() != null;
}
protected TagService getTagService() {
TagService tagService = Framework.getService(TagService.class);
return tagService.isEnabled() ? tagService : null;
}
/**
* Returns the list with distinct public tags (or owned by user) that are applied on the current document.
*/
@Factory(value = "currentDocumentTags", scope = EVENT)
public List<Tag> getDocumentTags() {
DocumentModel currentDocument = navigationContext.getCurrentDocument();
if (currentDocument == null) {
return new ArrayList<Tag>(0);
} else {
String docId = currentDocument.getId();
List<Tag> tags = getTagService().getDocumentTags(documentManager, docId, null);
Collections.sort(tags, Tag.LABEL_COMPARATOR);
return tags;
}
}
/**
* Performs the tagging on the current document.
*/
public String addTagging() {
tagLabel = cleanLabel(tagLabel);
String messageKey;
if (StringUtils.isBlank(tagLabel)) {
messageKey = "message.add.new.tagging.not.empty";
} else {
DocumentModel currentDocument = navigationContext.getCurrentDocument();
String docId = currentDocument.getId();
TagService tagService = getTagService();
tagService.tag(documentManager, docId, tagLabel, null);
if (currentDocument.isVersion()) {
DocumentModel liveDocument = documentManager.getSourceDocument(currentDocument.getRef());
if (!liveDocument.isCheckedOut()) {
tagService.tag(documentManager, liveDocument.getId(), tagLabel, null);
}
} else if (!currentDocument.isCheckedOut()) {
DocumentRef ref = documentManager.getBaseVersion(currentDocument.getRef());
if (ref instanceof IdRef) {
tagService.tag(documentManager, ref.toString(), tagLabel, null);
}
}
messageKey = "message.add.new.tagging";
// force invalidation
Contexts.getEventContext().remove("currentDocumentTags");
}
facesMessages.add(StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get(messageKey), tagLabel);
reset();
return null;
}
/**
* Removes a tagging from the current document.
*/
public String removeTagging(String label) {
DocumentModel currentDocument = navigationContext.getCurrentDocument();
String docId = currentDocument.getId();
TagService tagService = getTagService();
tagService.untag(documentManager, docId, label, null);
if (currentDocument.isVersion()) {
DocumentModel liveDocument = documentManager.getSourceDocument(currentDocument.getRef());
if (!liveDocument.isCheckedOut()) {
tagService.untag(documentManager, liveDocument.getId(), label, null);
}
} else if (!currentDocument.isCheckedOut()) {
DocumentRef ref = documentManager.getBaseVersion(currentDocument.getRef());
if (ref instanceof IdRef) {
tagService.untag(documentManager, ref.toString(), label, null);
}
}
reset();
// force invalidation
Contexts.getEventContext().remove("currentDocumentTags");
facesMessages.add(StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get("message.remove.tagging"),
label);
return null;
}
/**
* Returns tag cloud info for the whole repository. For performance reasons, the security on underlying documents is
* not tested.
*/
@Factory(value = "tagCloudOnAllDocuments", scope = EVENT)
public List<Tag> getPopularCloudOnAllDocuments() {
List<Tag> cloud = getTagService().getTagCloud(documentManager, null, null, Boolean.TRUE); // logarithmic 0-100
// normalization
// change weight to a font size
double min = 100;
double max = 200;
for (Tag tag : cloud) {
tag.setWeight((long) (min + tag.getWeight() * (max - min) / 100));
}
Collections.sort(cloud, Tag.LABEL_COMPARATOR);
// Collections.sort(cloud, Tag.WEIGHT_COMPARATOR);
return cloud;
}
public String listDocumentsForTag(String listLabel) {
this.listLabel = listLabel;
return TAG_SEARCH_RESULT_PAGE;
}
@Factory(value = "taggedDocuments", scope = EVENT)
public DocumentModelList getChildrenSelectModel() {
if (StringUtils.isBlank(listLabel)) {
return new DocumentModelListImpl(0);
} else {
List<String> ids = getTagService().getTagDocumentIds(documentManager, listLabel, null);
DocumentModelList docs = new DocumentModelListImpl(ids.size());
DocumentModel doc = null;
for (String id : ids) {
try {
doc = documentManager.getDocument(new IdRef(id));
} catch (DocumentNotFoundException e) {
log.error(e.getMessage(), e);
} catch (DocumentSecurityException e) {
// user don't have access to the document
doc = null;
}
if (doc != null) {
docs.add(doc);
doc = null;
}
}
return docs;
}
}
public String getListLabel() {
return listLabel;
}
public void setListLabel(String listLabel) {
this.listLabel = listLabel;
}
/**
* Returns <b>true</b> if the current logged user has permission to modify a tag that is applied on the current
* document.
*/
public boolean canModifyTag(Tag tag) {
return tag != null;
}
/**
* Resets the fields that are used for managing actions related to tagging.
*/
public void reset() {
tagLabel = null;
}
/**
* Used to decide whether the tagging UI field is shown or not.
*/
public void showAddTag(ActionEvent event) {
this.addTag = !this.addTag;
}
public String getTagLabel() {
return tagLabel;
}
/**
* @since 7.1
*/
public void setTagLabel(final String tagLabel) {
this.tagLabel = tagLabel;
}
public boolean getAddTag() {
return addTag;
}
public void setAddTag(boolean addTag) {
this.addTag = addTag;
}
public List<Tag> getSuggestions(Object input) {
String label = (String) input;
List<Tag> tags = getTagService().getSuggestions(documentManager, label, null);
Collections.sort(tags, Tag.LABEL_COMPARATOR);
if (tags.size() > 10) {
tags = tags.subList(0, 10);
}
// add the typed tag as first suggestion if we can add new tag
label = cleanLabel(label);
if (Boolean.TRUE.equals(canSelectNewTag) && !tags.contains(new Tag(label, 0))) {
tags.add(0, new Tag(label, -1));
}
return tags;
}
protected static String cleanLabel(String label) {
label = label.toLowerCase(); // lowercase
label = label.replace(" ", ""); // no spaces
label = label.replace("\\", ""); // dubious char
label = label.replace("'", ""); // dubious char
label = label.replace("%", ""); // dubious char
return label;
}
@SuppressWarnings("unchecked")
@Observer({ SELECTION_EDITED, DOCUMENTS_IMPORTED })
public void addTagsOnEvent(List<DocumentModel> documents, DocumentModel docModel) {
List<String> tags = (List<String>) docModel.getContextData("bulk_tags");
if (tags != null && !tags.isEmpty()) {
TagService tagService = Framework.getLocalService(TagService.class);
String username = documentManager.getPrincipal().getName();
for (DocumentModel doc : documents) {
for (String tag : tags) {
tagService.tag(documentManager, doc.getId(), tag, username);
}
}
}
}
@Observer(value = { EventNames.DOCUMENT_SELECTION_CHANGED }, create = false)
@BypassInterceptors
public void documentChanged() {
addTag = false;
}
}