001/* 002 * (C) Copyright 2006-2007 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 * Nuxeo - initial API and implementation 018 * 019 * $Id$ 020 */ 021 022package org.nuxeo.ecm.platform.comment.web; 023 024import java.util.ArrayList; 025import java.util.Calendar; 026import java.util.Collections; 027import java.util.HashMap; 028import java.util.List; 029import java.util.Map; 030 031import javax.faces.event.ActionEvent; 032 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.jboss.seam.annotations.Create; 036import org.jboss.seam.annotations.Destroy; 037import org.jboss.seam.annotations.In; 038import org.jboss.seam.annotations.Observer; 039import org.jboss.seam.annotations.intercept.BypassInterceptors; 040import org.jboss.seam.annotations.web.RequestParameter; 041import org.jboss.seam.contexts.Contexts; 042import org.nuxeo.ecm.core.api.CoreSession; 043import org.nuxeo.ecm.core.api.DocumentModel; 044import org.nuxeo.ecm.core.api.NuxeoException; 045import org.nuxeo.ecm.core.api.NuxeoPrincipal; 046import org.nuxeo.ecm.platform.actions.Action; 047import org.nuxeo.ecm.platform.comment.api.CommentableDocument; 048import org.nuxeo.ecm.platform.comment.workflow.utils.CommentsConstants; 049import org.nuxeo.ecm.platform.comment.workflow.utils.FollowTransitionUnrestricted; 050import org.nuxeo.ecm.platform.ui.web.api.NavigationContext; 051import org.nuxeo.ecm.platform.ui.web.api.WebActions; 052import org.nuxeo.ecm.webapp.helpers.EventNames; 053import org.nuxeo.ecm.webapp.security.UserSession; 054 055/** 056 * @author <a href="mailto:glefter@nuxeo.com">George Lefter</a> 057 */ 058public abstract class AbstractCommentManagerActionsBean implements CommentManagerActions { 059 060 protected static final String COMMENTS_ACTIONS = "COMMENT_ACTIONS"; 061 062 private static final Log log = LogFactory.getLog(AbstractCommentManagerActionsBean.class); 063 064 protected NuxeoPrincipal principal; 065 066 protected boolean principalIsAdmin; 067 068 protected boolean showCreateForm; 069 070 @In(create = true, required = false) 071 protected transient CoreSession documentManager; 072 073 @In(create = true) 074 protected transient WebActions webActions; 075 076 protected String newContent; 077 078 protected CommentableDocument commentableDoc; 079 080 protected List<UIComment> uiComments; 081 082 protected List<ThreadEntry> commentThread; 083 084 // the id of the comment to delete 085 @RequestParameter 086 protected String deleteCommentId; 087 088 // the id of the comment to reply to 089 @RequestParameter 090 protected String replyCommentId; 091 092 protected String savedReplyCommentId; 093 094 protected Map<String, UIComment> commentMap; 095 096 protected boolean commentStarted; 097 098 protected List<UIComment> flatComments; 099 100 @In(create = true) 101 protected UserSession userSession; 102 103 @In(create = true) 104 protected NavigationContext navigationContext; 105 106 @Override 107 @Create 108 public void initialize() { 109 log.debug("Initializing..."); 110 commentMap = new HashMap<String, UIComment>(); 111 showCreateForm = false; 112 113 principal = userSession.getCurrentNuxeoPrincipal(); 114 principalIsAdmin = principal.isAdministrator(); 115 } 116 117 @Override 118 @Destroy 119 public void destroy() { 120 commentMap = null; 121 log.debug("Removing Seam action listener..."); 122 } 123 124 @Override 125 public String getPrincipalName() { 126 return principal.getName(); 127 } 128 129 @Override 130 public boolean getPrincipalIsAdmin() { 131 return principalIsAdmin; 132 } 133 134 protected DocumentModel initializeComment(DocumentModel comment) { 135 if (comment != null) { 136 if (comment.getProperty("dublincore", "contributors") == null) { 137 String[] contributors = new String[1]; 138 contributors[0] = getPrincipalName(); 139 comment.setProperty("dublincore", "contributors", contributors); 140 } 141 if (comment.getProperty("dublincore", "created") == null) { 142 comment.setProperty("dublincore", "created", Calendar.getInstance()); 143 } 144 } 145 return comment; 146 } 147 148 public DocumentModel addComment(DocumentModel comment, DocumentModel docToComment) { 149 comment = initializeComment(comment); 150 UIComment parentComment = null; 151 if (savedReplyCommentId != null) { 152 parentComment = commentMap.get(savedReplyCommentId); 153 } 154 if (docToComment != null) { 155 commentableDoc = getCommentableDoc(docToComment); 156 } 157 if (commentableDoc == null) { 158 commentableDoc = getCommentableDoc(); 159 } 160 // what if commentableDoc is still null? shouldn't, but... 161 if (commentableDoc == null) { 162 throw new NuxeoException("Can't comment on null document"); 163 } 164 DocumentModel newComment; 165 if (parentComment != null) { 166 newComment = commentableDoc.addComment(parentComment.getComment(), comment); 167 } else { 168 newComment = commentableDoc.addComment(comment); 169 } 170 171 // automatically validate the comments 172 if (CommentsConstants.COMMENT_LIFECYCLE.equals(newComment.getLifeCyclePolicy())) { 173 new FollowTransitionUnrestricted(documentManager, newComment.getRef(), 174 CommentsConstants.TRANSITION_TO_PUBLISHED_STATE).runUnrestricted(); 175 } 176 177 // Events.instance().raiseEvent(CommentEvents.COMMENT_ADDED, null, 178 // newComment); 179 cleanContextVariable(); 180 181 return newComment; 182 } 183 184 @Override 185 public DocumentModel addComment(DocumentModel comment) { 186 return addComment(comment, null); 187 } 188 189 @Override 190 public String addComment() { 191 DocumentModel myComment = documentManager.createDocumentModel(CommentsConstants.COMMENT_DOC_TYPE); 192 193 myComment.setPropertyValue(CommentsConstants.COMMENT_AUTHOR, principal.getName()); 194 myComment.setPropertyValue(CommentsConstants.COMMENT_TEXT, newContent); 195 myComment.setPropertyValue(CommentsConstants.COMMENT_CREATION_DATE, Calendar.getInstance()); 196 myComment = addComment(myComment); 197 198 // do not navigate to newly-created comment, they are hidden documents 199 return null; 200 } 201 202 @Override 203 public String createComment(DocumentModel docToComment) { 204 DocumentModel myComment = documentManager.createDocumentModel(CommentsConstants.COMMENT_DOC_TYPE); 205 206 myComment.setProperty("comment", "author", principal.getName()); 207 myComment.setProperty("comment", "text", newContent); 208 myComment.setProperty("comment", "creationDate", Calendar.getInstance()); 209 myComment = addComment(myComment, docToComment); 210 211 // do not navigate to newly-created comment, they are hidden documents 212 return null; 213 } 214 215 @Override 216 @Observer(value = { EventNames.DOCUMENT_SELECTION_CHANGED, EventNames.CONTENT_ROOT_SELECTION_CHANGED, 217 EventNames.DOCUMENT_CHANGED }, create = false) 218 @BypassInterceptors 219 public void documentChanged() { 220 cleanContextVariable(); 221 } 222 223 protected CommentableDocument getCommentableDoc() { 224 if (commentableDoc == null) { 225 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 226 commentableDoc = currentDocument.getAdapter(CommentableDocument.class); 227 } 228 return commentableDoc; 229 } 230 231 protected CommentableDocument getCommentableDoc(DocumentModel doc) { 232 if (doc == null) { 233 doc = navigationContext.getCurrentDocument(); 234 } 235 commentableDoc = doc.getAdapter(CommentableDocument.class); 236 return commentableDoc; 237 } 238 239 /** 240 * Initializes uiComments with Comments of current document. 241 */ 242 @Override 243 public void initComments() { 244 DocumentModel currentDoc = navigationContext.getCurrentDocument(); 245 if (currentDoc == null) { 246 throw new NuxeoException("Unable to find current Document"); 247 } 248 initComments(currentDoc); 249 } 250 251 /** 252 * Initializes uiComments with Comments of current document. 253 */ 254 @Override 255 public void initComments(DocumentModel commentedDoc) { 256 commentableDoc = getCommentableDoc(commentedDoc); 257 if (uiComments == null) { 258 uiComments = new ArrayList<UIComment>(); 259 if (commentableDoc != null) { 260 List<DocumentModel> comments = commentableDoc.getComments(); 261 for (DocumentModel comment : comments) { 262 UIComment uiComment = createUIComment(null, comment); 263 uiComments.add(uiComment); 264 } 265 } 266 } 267 } 268 269 public List<UIComment> getComments(DocumentModel doc) { 270 List<UIComment> allComments = new ArrayList<UIComment>(); 271 commentableDoc = doc.getAdapter(CommentableDocument.class); 272 if (commentableDoc != null) { 273 List<DocumentModel> comments = commentableDoc.getComments(); 274 for (DocumentModel comment : comments) { 275 UIComment uiComment = createUIComment(null, comment); 276 allComments.add(uiComment); 277 } 278 } 279 return allComments; 280 } 281 282 /** 283 * Recursively retrieves all comments of a doc. 284 */ 285 @Override 286 public List<ThreadEntry> getCommentsAsThreadOnDoc(DocumentModel doc) { 287 List<ThreadEntry> allComments = new ArrayList<ThreadEntry>(); 288 List<UIComment> allUIComments = getComments(doc); 289 290 for (UIComment uiComment : allUIComments) { 291 allComments.add(new ThreadEntry(uiComment.getComment(), 0)); 292 if (uiComment.getChildren() != null) { 293 flattenTree(allComments, uiComment, 0); 294 } 295 } 296 return allComments; 297 } 298 299 @Override 300 public List<ThreadEntry> getCommentsAsThread(DocumentModel commentedDoc) { 301 if (commentThread != null) { 302 return commentThread; 303 } 304 commentThread = new ArrayList<ThreadEntry>(); 305 if (uiComments == null) { 306 initComments(commentedDoc); // Fetches all the comments associated 307 // with the 308 // document into uiComments (a list of comment 309 // roots). 310 } 311 for (UIComment uiComment : uiComments) { 312 commentThread.add(new ThreadEntry(uiComment.getComment(), 0)); 313 if (uiComment.getChildren() != null) { 314 flattenTree(commentThread, uiComment, 0); 315 } 316 } 317 return commentThread; 318 } 319 320 /** 321 * Visits a list of comment trees and puts them into a list of "ThreadEntry"s. 322 */ 323 public void flattenTree(List<ThreadEntry> commentThread, UIComment uiComment, int depth) { 324 List<UIComment> uiChildren = uiComment.getChildren(); 325 for (UIComment uiChild : uiChildren) { 326 commentThread.add(new ThreadEntry(uiChild.getComment(), depth + 1)); 327 if (uiChild.getChildren() != null) { 328 flattenTree(commentThread, uiChild, depth + 1); 329 } 330 } 331 } 332 333 /** 334 * Creates a UIComment wrapping "comment", having "parent" as parent. 335 */ 336 protected UIComment createUIComment(UIComment parent, DocumentModel comment) { 337 UIComment wrapper = new UIComment(parent, comment); 338 commentMap.put(wrapper.getId(), wrapper); 339 List<DocumentModel> children = commentableDoc.getComments(comment); 340 for (DocumentModel child : children) { 341 UIComment uiChild = createUIComment(wrapper, child); 342 wrapper.addChild(uiChild); 343 } 344 return wrapper; 345 } 346 347 @Override 348 public String deleteComment(String commentId) { 349 if ("".equals(commentId)) { 350 log.error("No comment id to delete"); 351 return null; 352 } 353 if (commentableDoc == null) { 354 log.error("Can't delete comments of null document"); 355 return null; 356 } 357 UIComment selectedComment = commentMap.get(commentId); 358 commentableDoc.removeComment(selectedComment.getComment()); 359 cleanContextVariable(); 360 // Events.instance().raiseEvent(CommentEvents.COMMENT_REMOVED, null, 361 // selectedComment.getComment()); 362 return null; 363 } 364 365 @Override 366 public String deleteComment() { 367 return deleteComment(deleteCommentId); 368 } 369 370 @Override 371 public String getNewContent() { 372 return newContent; 373 } 374 375 @Override 376 public void setNewContent(String newContent) { 377 this.newContent = newContent; 378 } 379 380 @Override 381 public String beginComment() { 382 commentStarted = true; 383 savedReplyCommentId = replyCommentId; 384 showCreateForm = false; 385 return null; 386 } 387 388 @Override 389 public String cancelComment() { 390 cleanContextVariable(); 391 return null; 392 } 393 394 @Override 395 public boolean getCommentStarted() { 396 return commentStarted; 397 } 398 399 /** 400 * Retrieves children for a given comment. 401 */ 402 public void getChildren(UIComment comment) { 403 assert comment != null; 404 405 List<UIComment> children = comment.getChildren(); 406 407 if (!children.isEmpty()) { 408 for (UIComment childComment : children) { 409 getChildren(childComment); 410 } 411 } 412 flatComments.add(comment); 413 } 414 415 @Override 416 @SuppressWarnings("unchecked") 417 public List<UIComment> getLastCommentsByDate(String commentNumber, DocumentModel commentedDoc) 418 { 419 int number = Integer.parseInt(commentNumber); 420 List<UIComment> comments = new ArrayList<UIComment>(); 421 flatComments = new ArrayList<UIComment>(); 422 423 // Initialize uiComments 424 initComments(commentedDoc); 425 426 if (number < 0 || uiComments.isEmpty()) { 427 return null; 428 } 429 for (UIComment comment : uiComments) { 430 getChildren(comment); 431 } 432 if (!flatComments.isEmpty()) { 433 Collections.sort(flatComments); 434 } 435 if (number > flatComments.size()) { 436 number = flatComments.size(); 437 } 438 for (int i = 0; i < number; i++) { 439 comments.add(flatComments.get(flatComments.size() - 1 - i)); 440 } 441 return comments; 442 } 443 444 @Override 445 public List<UIComment> getLastCommentsByDate(String commentNumber) { 446 return getLastCommentsByDate(commentNumber, null); 447 } 448 449 @Override 450 public String getSavedReplyCommentId() { 451 return savedReplyCommentId; 452 } 453 454 @Override 455 public void setSavedReplyCommentId(String savedReplyCommentId) { 456 this.savedReplyCommentId = savedReplyCommentId; 457 } 458 459 @Override 460 public List<Action> getActionsForComment() { 461 return webActions.getActionsList(COMMENTS_ACTIONS); 462 } 463 464 @Override 465 public List<Action> getActionsForComment(String category) { 466 return webActions.getActionsList(category); 467 } 468 469 @Override 470 public boolean getShowCreateForm() { 471 return showCreateForm; 472 } 473 474 @Override 475 public void setShowCreateForm(boolean flag) { 476 showCreateForm = flag; 477 } 478 479 @Override 480 public void toggleCreateForm(ActionEvent event) { 481 showCreateForm = !showCreateForm; 482 } 483 484 public void cleanContextVariable() { 485 commentableDoc = null; 486 uiComments = null; 487 commentThread = null; 488 showCreateForm = false; 489 commentStarted = false; 490 savedReplyCommentId = null; 491 newContent = null; 492 // NXP-11462: reset factory to force comment fetching after the new 493 // comment is added 494 Contexts.getEventContext().remove("documentThreadedComments"); 495 } 496 497}