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 */
020
021package org.nuxeo.ecm.platform.comment.impl;
022
023import static java.util.Collections.singletonList;
024import static java.util.Collections.singletonMap;
025import static java.util.stream.Collectors.collectingAndThen;
026import static java.util.stream.Collectors.toList;
027import static org.apache.commons.lang3.StringUtils.isBlank;
028import static org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonReader.applyDirtyPropertyValues;
029import static org.nuxeo.ecm.platform.comment.api.CommentConstants.COMMENT_ANCESTOR_IDS_PROPERTY;
030import static org.nuxeo.ecm.platform.comment.api.CommentConstants.COMMENT_AUTHOR_PROPERTY;
031import static org.nuxeo.ecm.platform.comment.api.CommentConstants.COMMENT_CREATION_DATE_PROPERTY;
032import static org.nuxeo.ecm.platform.comment.api.CommentConstants.COMMENT_PARENT_ID_PROPERTY;
033import static org.nuxeo.ecm.platform.comment.api.CommentConstants.COMMENT_SCHEMA;
034import static org.nuxeo.ecm.platform.comment.api.ExternalEntityConstants.EXTERNAL_ENTITY_FACET;
035import static org.nuxeo.ecm.platform.ec.notification.NotificationConstants.DISABLE_NOTIFICATION_SERVICE;
036import static org.nuxeo.ecm.platform.query.nxql.CoreQueryAndFetchPageProvider.CORE_SESSION_PROPERTY;
037
038import java.io.Serializable;
039import java.time.Instant;
040import java.util.ArrayList;
041import java.util.Collection;
042import java.util.Collections;
043import java.util.List;
044import java.util.Map;
045
046import org.nuxeo.ecm.core.api.CoreInstance;
047import org.nuxeo.ecm.core.api.CoreSession;
048import org.nuxeo.ecm.core.api.DocumentModel;
049import org.nuxeo.ecm.core.api.DocumentRef;
050import org.nuxeo.ecm.core.api.IdRef;
051import org.nuxeo.ecm.core.api.NuxeoPrincipal;
052import org.nuxeo.ecm.core.api.PartialList;
053import org.nuxeo.ecm.core.api.PathRef;
054import org.nuxeo.ecm.core.api.SortInfo;
055import org.nuxeo.ecm.core.api.security.SecurityConstants;
056import org.nuxeo.ecm.platform.comment.api.Comment;
057import org.nuxeo.ecm.platform.comment.api.CommentEvents;
058import org.nuxeo.ecm.platform.comment.api.exceptions.CommentNotFoundException;
059import org.nuxeo.ecm.platform.comment.api.exceptions.CommentSecurityException;
060import org.nuxeo.ecm.platform.query.api.PageProvider;
061import org.nuxeo.ecm.platform.query.api.PageProviderService;
062import org.nuxeo.runtime.api.Framework;
063
064/**
065 * Comment service implementation. The comments are linked together thanks to a parent document id property.
066 *
067 * @since 10.3
068 * @deprecated since 11.1, use {@link TreeCommentManager} instead
069 */
070@Deprecated(since = "11.1")
071public class PropertyCommentManager extends AbstractCommentManager {
072
073    /** @deprecated since 11.1, use {@link #GET_EXTERNAL_COMMENT_PAGEPROVIDER_NAME} instead */
074    @Deprecated(since = "11.1")
075    protected static final String GET_COMMENT_PAGEPROVIDER_NAME = "GET_COMMENT_AS_EXTERNAL_ENTITY";
076
077    /** @since 11.1 */
078    protected static final String GET_EXTERNAL_COMMENT_PAGEPROVIDER_NAME = "GET_EXTERNAL_COMMENT_BY_COMMENT_ANCESTOR";
079
080    protected static final String GET_COMMENTS_FOR_DOC_PAGEPROVIDER_NAME = "GET_COMMENTS_FOR_DOCUMENT";
081
082    protected static final String GET_COMMENTS_FOR_DOCS_PAGEPROVIDER_NAME = "GET_COMMENTS_FOR_DOCUMENTS_BY_COMMENT_ANCESTOR";
083
084    protected static final String HIDDEN_FOLDER_TYPE = "HiddenFolder";
085
086    protected static final String COMMENT_NAME = "comment";
087
088    @Override
089    @SuppressWarnings("unchecked")
090    public List<DocumentModel> getComments(CoreSession session, DocumentModel docModel)
091            throws CommentSecurityException {
092
093        DocumentRef docRef = getAncestorRef(session, docModel);
094
095        if (session.exists(docRef) && !session.hasPermission(docRef, SecurityConstants.READ)) {
096            throw new CommentSecurityException("The user " + session.getPrincipal().getName()
097                    + " does not have access to the comments of document " + docModel.getId());
098        }
099        PageProviderService ppService = Framework.getService(PageProviderService.class);
100        return CoreInstance.doPrivileged(session, s -> {
101            Map<String, Serializable> props = Collections.singletonMap(CORE_SESSION_PROPERTY, (Serializable) s);
102            PageProvider<DocumentModel> pageProvider = (PageProvider<DocumentModel>) ppService.getPageProvider(
103                    GET_COMMENTS_FOR_DOC_PAGEPROVIDER_NAME,
104                    singletonList(new SortInfo(COMMENT_CREATION_DATE_PROPERTY, true)), null, null, props,
105                    docModel.getId());
106            return pageProvider.getCurrentPage();
107        });
108    }
109
110    @Override
111    public List<DocumentModel> getComments(DocumentModel docModel, DocumentModel parent) {
112        throw new UnsupportedOperationException("This service implementation does not implement deprecated API.");
113    }
114
115    @Override
116    public DocumentModel createComment(DocumentModel docModel, String comment) {
117        throw new UnsupportedOperationException("This service implementation does not implement deprecated API.");
118    }
119
120    @Override
121    @SuppressWarnings("removal")
122    public DocumentModel createComment(DocumentModel docModel, String text, String author) {
123        throw new UnsupportedOperationException("This service implementation does not implement deprecated API.");
124    }
125
126    @Override
127    public DocumentModel createComment(DocumentModel docModel, DocumentModel commentModel)
128            throws CommentSecurityException {
129
130        NuxeoPrincipal principal = commentModel.getPrincipal();
131        // Open a session as system user since the parent document model can be a comment
132        CoreSession session = CoreInstance.getCoreSessionSystem(docModel.getRepositoryName());
133        DocumentRef docRef = getTopLevelDocumentRef(session, docModel.getRef());
134        if (!session.hasPermission(principal, docRef, SecurityConstants.READ)) {
135            throw new CommentSecurityException(
136                    "The user " + principal.getName() + " can not create comments on document " + docModel.getId());
137        }
138
139        String path = getCommentContainerPath(session, docModel.getId());
140
141        DocumentModel commentModelToCreate = session.createDocumentModel(path, COMMENT_NAME, commentModel.getType());
142        commentModelToCreate.copyContent(commentModel);
143        commentModelToCreate.setPropertyValue(COMMENT_PARENT_ID_PROPERTY, docModel.getId());
144        commentModelToCreate.setPropertyValue(COMMENT_ANCESTOR_IDS_PROPERTY,
145                computeAncestorIds(session, docModel.getId()));
146        DocumentModel comment = session.createDocument(commentModelToCreate);
147        comment.detach(true);
148        notifyEvent(session, CommentEvents.COMMENT_ADDED, docModel, comment);
149
150        return comment;
151    }
152
153    @Override
154    @SuppressWarnings("removal")
155    public DocumentModel createComment(DocumentModel docModel, DocumentModel parent, DocumentModel child) {
156        throw new UnsupportedOperationException("This service implementation does not implement deprecated API.");
157    }
158
159    @Override
160    @SuppressWarnings("removal")
161    public void deleteComment(DocumentModel docModel, DocumentModel comment) {
162        throw new UnsupportedOperationException("This service implementation does not implement deprecated API.");
163    }
164
165    @Override
166    @SuppressWarnings("removal")
167    public List<DocumentModel> getDocumentsForComment(DocumentModel comment) {
168        throw new UnsupportedOperationException("This service implementation does not implement deprecated API.");
169    }
170
171    @Override
172    public DocumentModel getThreadForComment(DocumentModel comment) throws CommentSecurityException {
173        return getThreadForComment(comment.getCoreSession(), comment);
174    }
175
176    @Override
177    public DocumentModel createLocatedComment(DocumentModel docModel, DocumentModel comment, String path) {
178        CoreSession session = docModel.getCoreSession();
179        DocumentRef docRef = getTopLevelDocumentRef(session, docModel.getRef());
180        if (!session.hasPermission(docRef, SecurityConstants.READ)) {
181            throw new CommentSecurityException("The user " + session.getPrincipal().getName()
182                    + " can not create comments on document " + docModel.getId());
183        }
184        return CoreInstance.doPrivileged(session, s -> {
185            DocumentModel commentModel = s.createDocumentModel(path, COMMENT_NAME, comment.getType());
186            commentModel.copyContent(comment);
187            commentModel.setPropertyValue(COMMENT_PARENT_ID_PROPERTY, docModel.getId());
188            commentModel.setPropertyValue(COMMENT_ANCESTOR_IDS_PROPERTY, computeAncestorIds(s, docModel.getId()));
189            commentModel = s.createDocument(commentModel);
190            notifyEvent(session, CommentEvents.COMMENT_ADDED, docModel, commentModel);
191            return commentModel;
192        });
193    }
194
195    @Override
196    public Comment createComment(CoreSession session, Comment comment)
197            throws CommentNotFoundException, CommentSecurityException {
198        String parentId = comment.getParentId();
199        DocumentRef parentRef = new IdRef(parentId);
200        // Parent document can be a comment, check existence as a privileged user
201        if (!CoreInstance.doPrivileged(session, s -> {
202            return s.exists(parentRef);
203        })) {
204            throw new CommentNotFoundException("The document or comment " + comment.getParentId() + " does not exist.");
205        }
206        DocumentRef ancestorRef = CoreInstance.doPrivileged(session, s -> {
207            DocumentModel parentDocModel = s.getDocument(parentRef);
208            if (parentDocModel.hasSchema(COMMENT_SCHEMA)) {
209                return getAncestorRef(s, parentDocModel);
210            }
211            return parentDocModel.getRef();
212        });
213        if (!session.hasPermission(ancestorRef, SecurityConstants.READ)) {
214            throw new CommentSecurityException("The user " + session.getPrincipal().getName()
215                    + " can not create comments on document " + parentId);
216        }
217
218        fillCommentForCreation(session, comment);
219
220        return CoreInstance.doPrivileged(session, s -> {
221            String path = getCommentContainerPath(s, parentId);
222            DocumentModel commentModel = session.createDocumentModel(path, COMMENT_NAME,
223                    comment.getDocument().getType());
224            if (comment.getDocument().hasFacet(EXTERNAL_ENTITY_FACET)) {
225                commentModel.addFacet(EXTERNAL_ENTITY_FACET);
226            }
227            applyDirtyPropertyValues(comment.getDocument(), commentModel);
228
229            // Compute the list of ancestor ids
230            commentModel.setPropertyValue(COMMENT_ANCESTOR_IDS_PROPERTY, computeAncestorIds(s, parentId));
231            commentModel = s.createDocument(commentModel);
232            notifyEvent(s, CommentEvents.COMMENT_ADDED, commentModel);
233            return commentModel.getAdapter(Comment.class);
234        });
235    }
236
237    @Override
238    public Comment getComment(CoreSession session, String commentId)
239            throws CommentNotFoundException, CommentSecurityException {
240        DocumentRef commentRef = new IdRef(commentId);
241        // Parent document can be a comment, check existence as a privileged user
242        if (!CoreInstance.doPrivileged(session, s -> {
243            return s.exists(commentRef);
244        })) {
245            throw new CommentNotFoundException("The comment " + commentId + " does not exist.");
246        }
247        NuxeoPrincipal principal = session.getPrincipal();
248        return CoreInstance.doPrivileged(session, s -> {
249            DocumentModel commentModel = s.getDocument(commentRef);
250            DocumentRef documentRef = getTopLevelDocumentRef(s, commentModel.getRef());
251            if (!s.hasPermission(principal, documentRef, SecurityConstants.READ)) {
252                throw new CommentSecurityException("The user " + principal.getName()
253                        + " does not have access to the comments of document " + documentRef.reference());
254            }
255
256            return commentModel.getAdapter(Comment.class);
257        });
258    }
259
260    @Override
261    @SuppressWarnings("unchecked")
262    public PartialList<Comment> getComments(CoreSession session, String documentId, Long pageSize,
263            Long currentPageIndex, boolean sortAscending) throws CommentSecurityException {
264        DocumentRef docRef = new IdRef(documentId);
265        PageProviderService ppService = Framework.getService(PageProviderService.class);
266        NuxeoPrincipal principal = session.getPrincipal();
267        return CoreInstance.doPrivileged(session, s -> {
268            if (s.exists(docRef)) {
269                DocumentRef ancestorRef = getTopLevelDocumentRef(s, docRef);
270                if (s.exists(ancestorRef) && !s.hasPermission(principal, ancestorRef, SecurityConstants.READ)) {
271                    throw new CommentSecurityException("The user " + principal.getName()
272                            + " does not have access to the comments of document " + documentId);
273                }
274            }
275            Map<String, Serializable> props = Collections.singletonMap(CORE_SESSION_PROPERTY, (Serializable) s);
276            PageProvider<DocumentModel> pageProvider = (PageProvider<DocumentModel>) ppService.getPageProvider(
277                    GET_COMMENTS_FOR_DOC_PAGEPROVIDER_NAME,
278                    singletonList(new SortInfo(COMMENT_CREATION_DATE_PROPERTY, sortAscending)), pageSize,
279                    currentPageIndex, props, documentId);
280            List<DocumentModel> commentList = pageProvider.getCurrentPage();
281            return commentList.stream()
282                              .map(doc -> doc.getAdapter(Comment.class))
283                              .collect(collectingAndThen(toList(),
284                                      list -> new PartialList<>(list, pageProvider.getResultsCount())));
285        });
286    }
287
288    @Override
289    public List<Comment> getComments(CoreSession session, Collection<String> documentIds) {
290        PageProviderService ppService = Framework.getService(PageProviderService.class);
291
292        Map<String, Serializable> props = Map.of(CORE_SESSION_PROPERTY, (Serializable) session);
293        @SuppressWarnings("unchecked")
294        var pageProvider = (PageProvider<DocumentModel>) ppService.getPageProvider(
295                GET_COMMENTS_FOR_DOCS_PAGEPROVIDER_NAME, null, null, null, props, new ArrayList<>(documentIds));
296        return pageProvider.getCurrentPage().stream().map(doc -> doc.getAdapter(Comment.class)).collect(toList());
297    }
298
299    @Override
300    public Comment updateComment(CoreSession session, String commentId, Comment comment)
301            throws CommentNotFoundException {
302        IdRef commentRef = new IdRef(commentId);
303        NuxeoPrincipal principal = session.getPrincipal();
304        return CoreInstance.doPrivileged(session, s -> {
305            if (!s.exists(commentRef)) {
306                throw new CommentNotFoundException("The comment " + commentId + " does not exist.");
307            }
308            DocumentModel commentModel = s.getDocument(commentRef);
309            if (!principal.isAdministrator()
310                    && !principal.getName().equals(commentModel.getPropertyValue(COMMENT_AUTHOR_PROPERTY))) {
311                throw new CommentSecurityException(
312                        "The user " + principal.getName() + " cannot edit comment " + commentId);
313            }
314
315            // Initiate Modification Date if it is not done yet
316            if (comment.getModificationDate() == null) {
317                comment.setModificationDate(Instant.now());
318            }
319            if (comment.getDocument().hasFacet(EXTERNAL_ENTITY_FACET)) {
320                commentModel.addFacet(EXTERNAL_ENTITY_FACET);
321            }
322            applyDirtyPropertyValues(comment.getDocument(), commentModel);
323            s.saveDocument(commentModel);
324            notifyEvent(s, CommentEvents.COMMENT_UPDATED, commentModel);
325            return commentModel.getAdapter(Comment.class);
326        });
327    }
328
329    @Override
330    public void deleteComment(CoreSession session, String commentId)
331            throws CommentNotFoundException, CommentSecurityException {
332        IdRef commentRef = new IdRef(commentId);
333        // Document can be a comment, check existence as a privileged user
334        if (!CoreInstance.doPrivileged(session, s -> {
335            return s.exists(commentRef);
336        })) {
337            throw new CommentNotFoundException("The comment " + commentId + " does not exist.");
338        }
339
340        NuxeoPrincipal principal = session.getPrincipal();
341        CoreInstance.doPrivileged(session, s -> {
342            DocumentModel comment = s.getDocument(commentRef);
343            String parentId = (String) comment.getPropertyValue(COMMENT_PARENT_ID_PROPERTY);
344            DocumentRef ancestorRef = getTopLevelDocumentRef(s, commentRef);
345            if (s.exists(ancestorRef) && !principal.isAdministrator()
346                    && !comment.getPropertyValue(COMMENT_AUTHOR_PROPERTY).equals(principal.getName())
347                    && !s.hasPermission(principal, ancestorRef, SecurityConstants.EVERYTHING)) {
348                throw new CommentSecurityException(
349                        "The user " + principal.getName() + " cannot delete comments of the document " + parentId);
350            }
351            // Allows the access to its data if needed in listeners
352            comment.detach(true);
353            s.removeDocument(commentRef);
354            notifyEvent(s, CommentEvents.COMMENT_REMOVED, comment);
355        });
356    }
357
358    @Override
359    public Comment getExternalComment(CoreSession session, String documentId, String entityId)
360            throws CommentNotFoundException {
361        DocumentModel commentModel = getExternalCommentModel(session, documentId, entityId);
362        if (commentModel == null) {
363            throw new CommentNotFoundException("The external comment " + entityId + " does not exist.");
364        }
365        String parentId = (String) commentModel.getPropertyValue(COMMENT_PARENT_ID_PROPERTY);
366        if (!session.hasPermission(getTopLevelDocumentRef(session, commentModel.getRef()), SecurityConstants.READ)) {
367            throw new CommentSecurityException("The user " + session.getPrincipal().getName()
368                    + " does not have access to the comments of document " + parentId);
369        }
370        return commentModel.getAdapter(Comment.class);
371    }
372
373    @Override
374    public Comment updateExternalComment(CoreSession session, String documentId, String entityId, Comment comment)
375            throws CommentNotFoundException {
376        DocumentModel commentModel = getExternalCommentModel(session, documentId, entityId);
377        if (commentModel == null) {
378            throw new CommentNotFoundException("The external comment " + entityId + " does not exist.");
379        }
380        NuxeoPrincipal principal = session.getPrincipal();
381        if (!principal.isAdministrator()
382                && !principal.getName().equals(commentModel.getPropertyValue(COMMENT_AUTHOR_PROPERTY))) {
383            throw new CommentSecurityException(
384                    "The user " + principal.getName() + " can not edit comments of document " + comment.getParentId());
385        }
386        return CoreInstance.doPrivileged(session, s -> {
387            // Initiate Modification Date if it is not done yet
388            if (comment.getModificationDate() == null) {
389                comment.setModificationDate(Instant.now());
390            }
391            applyDirtyPropertyValues(comment.getDocument(), commentModel);
392            s.saveDocument(commentModel);
393            notifyEvent(s, CommentEvents.COMMENT_UPDATED, commentModel);
394            return commentModel.getAdapter(Comment.class);
395        });
396    }
397
398    @Override
399    public void deleteExternalComment(CoreSession session, String documentId, String entityId)
400            throws CommentNotFoundException {
401        DocumentModel commentModel = getExternalCommentModel(session, documentId, entityId);
402        if (commentModel == null) {
403            throw new CommentNotFoundException("The external comment " + entityId + " does not exist.");
404        }
405        NuxeoPrincipal principal = session.getPrincipal();
406        String parentId = (String) commentModel.getPropertyValue(COMMENT_PARENT_ID_PROPERTY);
407        if (!principal.isAdministrator()
408                && !commentModel.getPropertyValue(COMMENT_AUTHOR_PROPERTY).equals(principal.getName())
409                && !session.hasPermission(principal, getTopLevelDocumentRef(session, commentModel.getRef()),
410                        SecurityConstants.EVERYTHING)) {
411            throw new CommentSecurityException(
412                    "The user " + principal.getName() + " can not delete comments of document " + parentId);
413        }
414        CoreInstance.doPrivileged(session, s -> {
415            DocumentModel comment = s.getDocument(commentModel.getRef());
416            comment.detach(true);
417            s.removeDocument(commentModel.getRef());
418            notifyEvent(s, CommentEvents.COMMENT_REMOVED, comment);
419        });
420    }
421
422    @Override
423    public boolean hasFeature(Feature feature) {
424        switch (feature) {
425        case COMMENTS_LINKED_WITH_PROPERTY:
426            return true;
427        case COMMENTS_ARE_SPECIAL_CHILDREN:
428            return false;
429        default:
430            throw new UnsupportedOperationException(feature.name());
431        }
432    }
433
434    @Override
435    protected DocumentModel getTopLevelDocument(CoreSession s, DocumentModel commentDoc) {
436        DocumentRef ancestorRef = getAncestorRef(s, commentDoc);
437        return s.getDocument(ancestorRef);
438    }
439
440    @SuppressWarnings("unchecked")
441    protected DocumentModel getExternalCommentModel(CoreSession session, String documentId, String entityId) {
442        return CoreInstance.doPrivileged(session, s -> {
443            PageProviderService ppService = Framework.getService(PageProviderService.class);
444            Map<String, Serializable> props = singletonMap(CORE_SESSION_PROPERTY, (Serializable) s);
445            PageProvider<DocumentModel> pageProvider;
446            // backward compatibility
447            if (isBlank(documentId)) {
448                pageProvider = (PageProvider<DocumentModel>) ppService.getPageProvider(GET_COMMENT_PAGEPROVIDER_NAME, null,
449                        1L, 0L, props, entityId);
450            } else {
451                pageProvider = (PageProvider<DocumentModel>) ppService.getPageProvider(
452                        GET_EXTERNAL_COMMENT_PAGEPROVIDER_NAME, null, 1L, 0L, props, documentId, entityId);
453            }
454            List<DocumentModel> results = pageProvider.getCurrentPage();
455            if (results.isEmpty()) {
456                return null;
457            }
458            return results.get(0);
459        });
460    }
461
462    protected String getCommentContainerPath(CoreSession session, String commentedDocumentId) {
463        return CoreInstance.doPrivileged(session, s -> {
464            // Create or retrieve the folder to store the comment.
465            // If the document is under a domain, the folder is a child of this domain.
466            // Otherwise, it is a child of the root document.
467            DocumentModel annotatedDoc = s.getDocument(new IdRef(commentedDocumentId));
468            String parentPath = "/";
469            if (annotatedDoc.getPath().segmentCount() > 1) {
470                parentPath += annotatedDoc.getPath().segment(0);
471            }
472            PathRef ref = new PathRef(parentPath, COMMENTS_DIRECTORY);
473            DocumentModel commentFolderDoc = s.createDocumentModel(parentPath, COMMENTS_DIRECTORY, HIDDEN_FOLDER_TYPE);
474            // No need to notify the creation of the Comments folder
475            commentFolderDoc.putContextData(DISABLE_NOTIFICATION_SERVICE, true);
476            s.getOrCreateDocument(commentFolderDoc);
477            s.save();
478            return ref.toString();
479        });
480    }
481
482    // former top level document method
483    protected DocumentRef getAncestorRef(CoreSession session, DocumentModel documentModel) {
484        if (!documentModel.hasSchema(COMMENT_SCHEMA)) {
485            return documentModel.getRef();
486        }
487        // thread is the higher comment in a succession of replies
488        DocumentModel thread = getThreadForComment(session, documentModel);
489        return new IdRef((String) thread.getPropertyValue(COMMENT_PARENT_ID_PROPERTY));
490    }
491
492    protected DocumentModel getThreadForComment(CoreSession s, DocumentModel comment) throws CommentSecurityException {
493        NuxeoPrincipal principal = s.getPrincipal();
494        return CoreInstance.doPrivileged(s, session -> {
495            DocumentModel thread = comment;
496            DocumentModel parent = s.getDocument(
497                    new IdRef((String) thread.getPropertyValue(COMMENT_PARENT_ID_PROPERTY)));
498            if (parent.hasSchema(COMMENT_SCHEMA)) {
499                thread = getThreadForComment(parent);
500            }
501            DocumentRef ancestorRef = s.getDocument(
502                    new IdRef((String) thread.getPropertyValue(COMMENT_PARENT_ID_PROPERTY))).getRef();
503            if (!s.hasPermission(principal, ancestorRef, SecurityConstants.READ)) {
504                throw new CommentSecurityException("The user " + principal.getName()
505                        + " does not have access to the comments of document " + ancestorRef.reference());
506            }
507            return thread;
508        });
509    }
510
511    @Override
512    protected DocumentModel getCommentedDocument(CoreSession session, DocumentModel commentDoc) {
513        String parentId = (String) commentDoc.getPropertyValue(COMMENT_PARENT_ID_PROPERTY);
514        return session.getDocument(new IdRef(parentId));
515    }
516}