001/* 002 * (C) Copyright 2018-2020 Nuxeo (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 * Funsho David 018 * Nuno Cunha <ncunha@nuxeo.com> 019 * Nour AL KOTOB 020 */ 021 022package org.nuxeo.ecm.platform.comment.impl; 023 024import static java.util.Objects.requireNonNull; 025import static org.apache.commons.lang3.StringUtils.isBlank; 026import static org.nuxeo.ecm.platform.comment.api.CommentConstants.COMMENT_AUTHOR_PROPERTY; 027import static org.nuxeo.ecm.platform.comment.api.CommentConstants.COMMENT_PARENT_ID_PROPERTY; 028import static org.nuxeo.ecm.platform.comment.api.CommentConstants.COMMENT_SCHEMA; 029import static org.nuxeo.ecm.platform.comment.api.CommentConstants.COMMENT_TEXT_PROPERTY; 030 031import java.io.Serializable; 032import java.time.Instant; 033import java.util.HashMap; 034import java.util.HashSet; 035import java.util.List; 036import java.util.Map; 037import java.util.Set; 038 039import org.apache.commons.lang3.ArrayUtils; 040import org.apache.commons.lang3.StringUtils; 041import org.apache.logging.log4j.LogManager; 042import org.apache.logging.log4j.Logger; 043import org.nuxeo.ecm.core.api.CoreInstance; 044import org.nuxeo.ecm.core.api.CoreSession; 045import org.nuxeo.ecm.core.api.DocumentModel; 046import org.nuxeo.ecm.core.api.DocumentRef; 047import org.nuxeo.ecm.core.api.IdRef; 048import org.nuxeo.ecm.core.api.NuxeoPrincipal; 049import org.nuxeo.ecm.core.api.PartialList; 050import org.nuxeo.ecm.core.api.PropertyException; 051import org.nuxeo.ecm.core.api.security.ACE; 052import org.nuxeo.ecm.core.api.security.ACL; 053import org.nuxeo.ecm.core.api.security.ACP; 054import org.nuxeo.ecm.core.api.security.SecurityConstants; 055import org.nuxeo.ecm.core.api.security.impl.ACLImpl; 056import org.nuxeo.ecm.core.api.security.impl.ACPImpl; 057import org.nuxeo.ecm.core.event.Event; 058import org.nuxeo.ecm.core.event.EventProducer; 059import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 060import org.nuxeo.ecm.platform.comment.api.Comment; 061import org.nuxeo.ecm.platform.comment.api.CommentConstants; 062import org.nuxeo.ecm.platform.comment.api.CommentManager; 063import org.nuxeo.ecm.platform.comment.api.exceptions.CommentNotFoundException; 064import org.nuxeo.ecm.platform.comment.api.exceptions.CommentSecurityException; 065import org.nuxeo.ecm.platform.usermanager.UserManager; 066import org.nuxeo.runtime.api.Framework; 067 068/** 069 * @since 10.3 070 */ 071public abstract class AbstractCommentManager implements CommentManager { 072 073 private static final Logger log = LogManager.getLogger(AbstractCommentManager.class); 074 075 /** @since 11.1 */ 076 public static final String COMMENTS_DIRECTORY = "Comments"; 077 078 @Override 079 public List<DocumentModel> getComments(DocumentModel docModel) { 080 return getComments(docModel.getCoreSession(), docModel); 081 } 082 083 @Override 084 @SuppressWarnings("removal") 085 public List<DocumentModel> getComments(DocumentModel docModel, DocumentModel parent) { 086 return getComments(docModel); 087 } 088 089 @Override 090 public List<Comment> getComments(CoreSession session, String documentId) { 091 return getComments(session, documentId, 0L, 0L, true); 092 } 093 094 @Override 095 public List<Comment> getComments(CoreSession session, String documentId, boolean sortAscending) { 096 return getComments(session, documentId, 0L, 0L, sortAscending); 097 } 098 099 @Override 100 public PartialList<Comment> getComments(CoreSession session, String documentId, Long pageSize, 101 Long currentPageIndex) { 102 return getComments(session, documentId, pageSize, currentPageIndex, true); 103 } 104 105 @Override 106 public DocumentRef getTopLevelDocumentRef(CoreSession session, DocumentRef commentRef) { 107 NuxeoPrincipal principal = session.getPrincipal(); 108 return CoreInstance.doPrivileged(session, s -> { 109 if (!s.exists(commentRef)) { 110 throw new CommentNotFoundException(String.format("The comment %s does not exist.", commentRef)); 111 } 112 113 DocumentModel commentDoc = s.getDocument(commentRef); 114 DocumentModel topLevelDoc = getTopLevelDocument(s, commentDoc); 115 DocumentRef topLevelDocRef = topLevelDoc.getRef(); 116 117 if (!s.hasPermission(principal, topLevelDocRef, SecurityConstants.READ)) { 118 throw new CommentSecurityException("The user " + principal.getName() 119 + " does not have access to the comments of document " + topLevelDocRef); 120 } 121 122 return topLevelDocRef; 123 }); 124 } 125 126 /** 127 * Notifies the event of type {@code eventType} on the given {@code commentDoc}. 128 * 129 * @param session the session 130 * @param eventType the event type to fire 131 * @param commentDoc the document model of the comment 132 * @implSpec This method uses internally {@link #notifyEvent(CoreSession, String, DocumentModel, DocumentModel)} 133 * @since 11.1 134 */ 135 protected void notifyEvent(CoreSession session, String eventType, DocumentModel commentDoc) { 136 DocumentModel commentedDoc = getCommentedDocument(session, commentDoc); 137 notifyEvent(session, eventType, commentedDoc, commentDoc); 138 } 139 140 protected void notifyEvent(CoreSession session, String eventType, DocumentModel commentedDoc, 141 DocumentModel commentDoc) { 142 DocumentModel topLevelDoc = getTopLevelDocument(session, commentDoc); 143 notifyEvent(session, eventType, topLevelDoc, commentedDoc, commentDoc); 144 } 145 146 /** 147 * @since 11.1 148 */ 149 protected void notifyEvent(CoreSession session, String eventType, DocumentModel topLevelDoc, 150 DocumentModel commentedDoc, DocumentModel commentDoc) { 151 requireNonNull(eventType); 152 UserManager userManager = Framework.getService(UserManager.class); 153 NuxeoPrincipal principal = null; 154 if (userManager != null) { 155 principal = userManager.getPrincipal((String) commentDoc.getPropertyValue(COMMENT_AUTHOR_PROPERTY)); 156 if (principal == null) { 157 try { 158 principal = getAuthor(commentDoc); 159 } catch (PropertyException e) { 160 log.error("Error building principal for comment author", e); 161 return; 162 } 163 } 164 } 165 DocumentEventContext ctx = new DocumentEventContext(session, principal, commentedDoc); 166 Map<String, Serializable> props = new HashMap<>(); 167 props.put(CommentConstants.TOP_LEVEL_DOCUMENT, topLevelDoc); 168 props.put(CommentConstants.PARENT_COMMENT, commentedDoc); 169 // simplifies template checks and vars expansion 170 if (!topLevelDoc.equals(commentedDoc)) { 171 String commentAuthor; 172 NuxeoPrincipal commentPrincipal = getAuthor(commentedDoc); 173 if (commentPrincipal != null) { 174 commentAuthor = commentPrincipal.getFirstName(); 175 commentAuthor = isBlank(commentAuthor) ? commentPrincipal.getName() : commentAuthor; 176 } else { 177 commentAuthor = ((String[]) commentedDoc.getPropertyValue("dc:contributors"))[0]; 178 } 179 props.put(CommentConstants.PARENT_COMMENT_AUTHOR, commentAuthor); 180 } 181 props.put(CommentConstants.COMMENT_DOCUMENT, commentDoc); 182 props.put(CommentConstants.COMMENT, commentDoc.getPropertyValue(COMMENT_TEXT_PROPERTY)); 183 // Keep comment_text for compatibility 184 props.put(CommentConstants.COMMENT_TEXT, commentDoc.getPropertyValue(COMMENT_TEXT_PROPERTY)); 185 props.put("category", CommentConstants.EVENT_COMMENT_CATEGORY); 186 ctx.setProperties(props); 187 Event event = ctx.newEvent(eventType); 188 189 EventProducer evtProducer = Framework.getService(EventProducer.class); 190 evtProducer.fireEvent(event); 191 } 192 193 protected abstract DocumentModel getTopLevelDocument(CoreSession session, DocumentModel commentDoc); 194 195 protected abstract DocumentModel getCommentedDocument(CoreSession session, DocumentModel commentDoc); 196 197 protected NuxeoPrincipal getAuthor(DocumentModel docModel) { 198 String author = null; 199 if (docModel.hasSchema(COMMENT_SCHEMA)) { 200 // means annotation / comment 201 author = (String) docModel.getPropertyValue(COMMENT_AUTHOR_PROPERTY); 202 } 203 if (StringUtils.isBlank(author)) { 204 String[] contributors = (String[]) docModel.getPropertyValue("dc:contributors"); 205 if (ArrayUtils.isNotEmpty(contributors)) { 206 author = contributors[0]; 207 } 208 } 209 210 NuxeoPrincipal principal = Framework.getService(UserManager.class).getPrincipal(author); 211 // If principal doesn't exist anymore 212 if (principal == null) { 213 log.debug("Principal not found: {}", author); 214 } 215 return principal; 216 } 217 218 protected void setFolderPermissions(CoreSession session, DocumentModel documentModel) { 219 ACP acp = documentModel.getACP(); 220 acp.blockInheritance(ACL.LOCAL_ACL, SecurityConstants.SYSTEM_USERNAME); 221 documentModel.setACP(acp, true); 222 } 223 224 /** 225 * @deprecated since 11.1. Not used anymore 226 */ 227 @Deprecated(since = "11.1") 228 protected void setCommentPermissions(CoreSession session, DocumentModel documentModel) { 229 ACP acp = new ACPImpl(); 230 ACE grantRead = new ACE(SecurityConstants.EVERYONE, SecurityConstants.READ, true); 231 ACE grantRemove = new ACE("members", SecurityConstants.REMOVE, true); 232 ACL acl = new ACLImpl(); 233 acl.setACEs(new ACE[] { grantRead, grantRemove }); 234 acp.addACL(acl); 235 session.setACP(documentModel.getRef(), acp, true); 236 } 237 238 protected void fillCommentForCreation(CoreSession session, Comment comment) { 239 // Initiate Author if it is not done yet 240 if (comment.getAuthor() == null) { 241 comment.setAuthor(session.getPrincipal().getName()); 242 } 243 244 // Initiate Creation Date if it is not done yet 245 if (comment.getCreationDate() == null) { 246 comment.setCreationDate(Instant.now()); 247 } 248 249 // Initiate Modification Date if it is not done yet 250 if (comment.getModificationDate() == null) { 251 comment.setModificationDate(Instant.now()); 252 } 253 } 254 255 /** 256 * @param session the session allowing to get parent documents, depending on implementation it should be privileged 257 */ 258 @SuppressWarnings("unchecked") 259 protected <S extends Set<String> & Serializable> S computeAncestorIds(CoreSession session, String parentId) { 260 Set<String> ancestorIds = new HashSet<>(); 261 ancestorIds.add(parentId); 262 DocumentRef parentRef = new IdRef(parentId); 263 while (session.exists(parentRef) && session.getDocument(parentRef).hasSchema(COMMENT_SCHEMA)) { 264 parentId = (String) session.getDocument(parentRef).getPropertyValue(COMMENT_PARENT_ID_PROPERTY); 265 ancestorIds.add(parentId); 266 parentRef = new IdRef(parentId); 267 } 268 return (S) ancestorIds; 269 } 270 271}