/* * (C) Copyright 2006-2007 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: * Nuxeo - initial API and implementation * * $Id$ */ package org.nuxeo.ecm.platform.comment.web; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.faces.event.ActionEvent; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.seam.annotations.Create; import org.jboss.seam.annotations.Destroy; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Observer; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.annotations.web.RequestParameter; import org.jboss.seam.contexts.Contexts; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.ecm.core.api.NuxeoPrincipal; import org.nuxeo.ecm.platform.actions.Action; import org.nuxeo.ecm.platform.comment.api.CommentableDocument; import org.nuxeo.ecm.platform.comment.workflow.utils.CommentsConstants; import org.nuxeo.ecm.platform.comment.workflow.utils.FollowTransitionUnrestricted; import org.nuxeo.ecm.platform.ui.web.api.NavigationContext; import org.nuxeo.ecm.platform.ui.web.api.WebActions; import org.nuxeo.ecm.webapp.helpers.EventNames; import org.nuxeo.ecm.webapp.security.UserSession; /** * @author <a href="mailto:glefter@nuxeo.com">George Lefter</a> */ public abstract class AbstractCommentManagerActionsBean implements CommentManagerActions { protected static final String COMMENTS_ACTIONS = "COMMENT_ACTIONS"; private static final Log log = LogFactory.getLog(AbstractCommentManagerActionsBean.class); protected NuxeoPrincipal principal; protected boolean principalIsAdmin; protected boolean showCreateForm; @In(create = true, required = false) protected transient CoreSession documentManager; @In(create = true) protected transient WebActions webActions; protected String newContent; protected CommentableDocument commentableDoc; protected List<UIComment> uiComments; protected List<ThreadEntry> commentThread; // the id of the comment to delete @RequestParameter protected String deleteCommentId; // the id of the comment to reply to @RequestParameter protected String replyCommentId; protected String savedReplyCommentId; protected Map<String, UIComment> commentMap; protected boolean commentStarted; protected List<UIComment> flatComments; @In(create = true) protected UserSession userSession; @In(create = true) protected NavigationContext navigationContext; @Override @Create public void initialize() { log.debug("Initializing..."); commentMap = new HashMap<String, UIComment>(); showCreateForm = false; principal = userSession.getCurrentNuxeoPrincipal(); principalIsAdmin = principal.isAdministrator(); } @Override @Destroy public void destroy() { commentMap = null; log.debug("Removing Seam action listener..."); } @Override public String getPrincipalName() { return principal.getName(); } @Override public boolean getPrincipalIsAdmin() { return principalIsAdmin; } protected DocumentModel initializeComment(DocumentModel comment) { if (comment != null) { if (comment.getProperty("dublincore", "contributors") == null) { String[] contributors = new String[1]; contributors[0] = getPrincipalName(); comment.setProperty("dublincore", "contributors", contributors); } if (comment.getProperty("dublincore", "created") == null) { comment.setProperty("dublincore", "created", Calendar.getInstance()); } } return comment; } public DocumentModel addComment(DocumentModel comment, DocumentModel docToComment) { comment = initializeComment(comment); UIComment parentComment = null; if (savedReplyCommentId != null) { parentComment = commentMap.get(savedReplyCommentId); } if (docToComment != null) { commentableDoc = getCommentableDoc(docToComment); } if (commentableDoc == null) { commentableDoc = getCommentableDoc(); } // what if commentableDoc is still null? shouldn't, but... if (commentableDoc == null) { throw new NuxeoException("Can't comment on null document"); } DocumentModel newComment; if (parentComment != null) { newComment = commentableDoc.addComment(parentComment.getComment(), comment); } else { newComment = commentableDoc.addComment(comment); } // automatically validate the comments if (CommentsConstants.COMMENT_LIFECYCLE.equals(newComment.getLifeCyclePolicy())) { new FollowTransitionUnrestricted(documentManager, newComment.getRef(), CommentsConstants.TRANSITION_TO_PUBLISHED_STATE).runUnrestricted(); } // Events.instance().raiseEvent(CommentEvents.COMMENT_ADDED, null, // newComment); cleanContextVariable(); return newComment; } @Override public DocumentModel addComment(DocumentModel comment) { return addComment(comment, null); } @Override public String addComment() { DocumentModel myComment = documentManager.createDocumentModel(CommentsConstants.COMMENT_DOC_TYPE); myComment.setPropertyValue(CommentsConstants.COMMENT_AUTHOR, principal.getName()); myComment.setPropertyValue(CommentsConstants.COMMENT_TEXT, newContent); myComment.setPropertyValue(CommentsConstants.COMMENT_CREATION_DATE, Calendar.getInstance()); myComment = addComment(myComment); // do not navigate to newly-created comment, they are hidden documents return null; } @Override public String createComment(DocumentModel docToComment) { DocumentModel myComment = documentManager.createDocumentModel(CommentsConstants.COMMENT_DOC_TYPE); myComment.setProperty("comment", "author", principal.getName()); myComment.setProperty("comment", "text", newContent); myComment.setProperty("comment", "creationDate", Calendar.getInstance()); myComment = addComment(myComment, docToComment); // do not navigate to newly-created comment, they are hidden documents return null; } @Override @Observer(value = { EventNames.DOCUMENT_SELECTION_CHANGED, EventNames.CONTENT_ROOT_SELECTION_CHANGED, EventNames.DOCUMENT_CHANGED }, create = false) @BypassInterceptors public void documentChanged() { cleanContextVariable(); } protected CommentableDocument getCommentableDoc() { if (commentableDoc == null) { DocumentModel currentDocument = navigationContext.getCurrentDocument(); commentableDoc = currentDocument.getAdapter(CommentableDocument.class); } return commentableDoc; } protected CommentableDocument getCommentableDoc(DocumentModel doc) { if (doc == null) { doc = navigationContext.getCurrentDocument(); } commentableDoc = doc.getAdapter(CommentableDocument.class); return commentableDoc; } /** * Initializes uiComments with Comments of current document. */ @Override public void initComments() { DocumentModel currentDoc = navigationContext.getCurrentDocument(); if (currentDoc == null) { throw new NuxeoException("Unable to find current Document"); } initComments(currentDoc); } /** * Initializes uiComments with Comments of current document. */ @Override public void initComments(DocumentModel commentedDoc) { commentableDoc = getCommentableDoc(commentedDoc); if (uiComments == null) { uiComments = new ArrayList<UIComment>(); if (commentableDoc != null) { List<DocumentModel> comments = commentableDoc.getComments(); for (DocumentModel comment : comments) { UIComment uiComment = createUIComment(null, comment); uiComments.add(uiComment); } } } } public List<UIComment> getComments(DocumentModel doc) { List<UIComment> allComments = new ArrayList<UIComment>(); commentableDoc = doc.getAdapter(CommentableDocument.class); if (commentableDoc != null) { List<DocumentModel> comments = commentableDoc.getComments(); for (DocumentModel comment : comments) { UIComment uiComment = createUIComment(null, comment); allComments.add(uiComment); } } return allComments; } /** * Recursively retrieves all comments of a doc. */ @Override public List<ThreadEntry> getCommentsAsThreadOnDoc(DocumentModel doc) { List<ThreadEntry> allComments = new ArrayList<ThreadEntry>(); List<UIComment> allUIComments = getComments(doc); for (UIComment uiComment : allUIComments) { allComments.add(new ThreadEntry(uiComment.getComment(), 0)); if (uiComment.getChildren() != null) { flattenTree(allComments, uiComment, 0); } } return allComments; } @Override public List<ThreadEntry> getCommentsAsThread(DocumentModel commentedDoc) { if (commentThread != null) { return commentThread; } commentThread = new ArrayList<ThreadEntry>(); if (uiComments == null) { initComments(commentedDoc); // Fetches all the comments associated // with the // document into uiComments (a list of comment // roots). } for (UIComment uiComment : uiComments) { commentThread.add(new ThreadEntry(uiComment.getComment(), 0)); if (uiComment.getChildren() != null) { flattenTree(commentThread, uiComment, 0); } } return commentThread; } /** * Visits a list of comment trees and puts them into a list of "ThreadEntry"s. */ public void flattenTree(List<ThreadEntry> commentThread, UIComment uiComment, int depth) { List<UIComment> uiChildren = uiComment.getChildren(); for (UIComment uiChild : uiChildren) { commentThread.add(new ThreadEntry(uiChild.getComment(), depth + 1)); if (uiChild.getChildren() != null) { flattenTree(commentThread, uiChild, depth + 1); } } } /** * Creates a UIComment wrapping "comment", having "parent" as parent. */ protected UIComment createUIComment(UIComment parent, DocumentModel comment) { UIComment wrapper = new UIComment(parent, comment); commentMap.put(wrapper.getId(), wrapper); List<DocumentModel> children = commentableDoc.getComments(comment); for (DocumentModel child : children) { UIComment uiChild = createUIComment(wrapper, child); wrapper.addChild(uiChild); } return wrapper; } @Override public String deleteComment(String commentId) { if ("".equals(commentId)) { log.error("No comment id to delete"); return null; } if (commentableDoc == null) { log.error("Can't delete comments of null document"); return null; } UIComment selectedComment = commentMap.get(commentId); commentableDoc.removeComment(selectedComment.getComment()); cleanContextVariable(); // Events.instance().raiseEvent(CommentEvents.COMMENT_REMOVED, null, // selectedComment.getComment()); return null; } @Override public String deleteComment() { return deleteComment(deleteCommentId); } @Override public String getNewContent() { return newContent; } @Override public void setNewContent(String newContent) { this.newContent = newContent; } @Override public String beginComment() { commentStarted = true; savedReplyCommentId = replyCommentId; showCreateForm = false; return null; } @Override public String cancelComment() { cleanContextVariable(); return null; } @Override public boolean getCommentStarted() { return commentStarted; } /** * Retrieves children for a given comment. */ public void getChildren(UIComment comment) { assert comment != null; List<UIComment> children = comment.getChildren(); if (!children.isEmpty()) { for (UIComment childComment : children) { getChildren(childComment); } } flatComments.add(comment); } @Override @SuppressWarnings("unchecked") public List<UIComment> getLastCommentsByDate(String commentNumber, DocumentModel commentedDoc) { int number = Integer.parseInt(commentNumber); List<UIComment> comments = new ArrayList<UIComment>(); flatComments = new ArrayList<UIComment>(); // Initialize uiComments initComments(commentedDoc); if (number < 0 || uiComments.isEmpty()) { return null; } for (UIComment comment : uiComments) { getChildren(comment); } if (!flatComments.isEmpty()) { Collections.sort(flatComments); } if (number > flatComments.size()) { number = flatComments.size(); } for (int i = 0; i < number; i++) { comments.add(flatComments.get(flatComments.size() - 1 - i)); } return comments; } @Override public List<UIComment> getLastCommentsByDate(String commentNumber) { return getLastCommentsByDate(commentNumber, null); } @Override public String getSavedReplyCommentId() { return savedReplyCommentId; } @Override public void setSavedReplyCommentId(String savedReplyCommentId) { this.savedReplyCommentId = savedReplyCommentId; } @Override public List<Action> getActionsForComment() { return webActions.getActionsList(COMMENTS_ACTIONS); } @Override public List<Action> getActionsForComment(String category) { return webActions.getActionsList(category); } @Override public boolean getShowCreateForm() { return showCreateForm; } @Override public void setShowCreateForm(boolean flag) { showCreateForm = flag; } @Override public void toggleCreateForm(ActionEvent event) { showCreateForm = !showCreateForm; } public void cleanContextVariable() { commentableDoc = null; uiComments = null; commentThread = null; showCreateForm = false; commentStarted = false; savedReplyCommentId = null; newContent = null; // NXP-11462: reset factory to force comment fetching after the new // comment is added Contexts.getEventContext().remove("documentThreadedComments"); } }