001/*
002 * (C) Copyright 2018 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.singletonMap;
024import static org.nuxeo.ecm.platform.comment.api.AnnotationConstants.ANNOTATION_DOC_TYPE;
025import static org.nuxeo.ecm.platform.comment.api.AnnotationConstants.ANNOTATION_XPATH_PROPERTY;
026import static org.nuxeo.ecm.platform.comment.api.CommentManager.Feature.COMMENTS_LINKED_WITH_PROPERTY;
027import static org.nuxeo.ecm.platform.comment.api.ExternalEntityConstants.EXTERNAL_ENTITY_FACET;
028import static org.nuxeo.ecm.platform.comment.workflow.utils.CommentsConstants.COMMENT_AUTHOR;
029import static org.nuxeo.ecm.platform.comment.workflow.utils.CommentsConstants.COMMENT_PARENT_ID;
030import static org.nuxeo.ecm.platform.query.nxql.CoreQueryAndFetchPageProvider.CORE_SESSION_PROPERTY;
031
032import java.io.Serializable;
033import java.util.Collections;
034import java.util.List;
035import java.util.Map;
036import java.util.stream.Collectors;
037
038import org.apache.commons.logging.Log;
039import org.apache.commons.logging.LogFactory;
040import org.nuxeo.ecm.core.api.CoreInstance;
041import org.nuxeo.ecm.core.api.CoreSession;
042import org.nuxeo.ecm.core.api.DocumentModel;
043import org.nuxeo.ecm.core.api.DocumentNotFoundException;
044import org.nuxeo.ecm.core.api.DocumentRef;
045import org.nuxeo.ecm.core.api.IdRef;
046import org.nuxeo.ecm.core.api.NuxeoPrincipal;
047import org.nuxeo.ecm.core.api.security.SecurityConstants;
048import org.nuxeo.ecm.platform.comment.api.Annotation;
049import org.nuxeo.ecm.platform.comment.api.AnnotationService;
050import org.nuxeo.ecm.platform.comment.api.CommentManager;
051import org.nuxeo.ecm.platform.comment.api.Comments;
052import org.nuxeo.ecm.platform.comment.api.ExternalEntity;
053import org.nuxeo.ecm.platform.comment.api.exceptions.CommentNotFoundException;
054import org.nuxeo.ecm.platform.comment.api.exceptions.CommentSecurityException;
055import org.nuxeo.ecm.platform.query.api.PageProvider;
056import org.nuxeo.ecm.platform.query.api.PageProviderService;
057import org.nuxeo.runtime.api.Framework;
058import org.nuxeo.runtime.model.DefaultComponent;
059
060/**
061 * @since 10.1
062 */
063public class AnnotationServiceImpl extends DefaultComponent implements AnnotationService {
064
065    private static final Log log = LogFactory.getLog(AnnotationServiceImpl.class);
066
067    protected static final String GET_ANNOTATION_PAGEPROVIDER_NAME = "GET_ANNOTATION_AS_EXTERNAL_ENTITY";
068
069    protected static final String GET_ANNOTATIONS_FOR_DOC_PAGEPROVIDER_NAME = "GET_ANNOTATIONS_FOR_DOCUMENT";
070
071    @Override
072    public Annotation createAnnotation(CoreSession session, Annotation annotation) throws CommentSecurityException {
073        String parentId = annotation.getParentId();
074        if (!session.hasPermission(new IdRef(parentId), SecurityConstants.READ)) {
075            throw new CommentSecurityException("The user " + session.getPrincipal().getName()
076                    + " can not create annotations on document " + parentId);
077        }
078        return CoreInstance.doPrivileged(session, s -> {
079            // Create base comment in the annotation
080            DocumentModel docToAnnotate = s.getDocument(new IdRef(annotation.getParentId()));
081            DocumentModel annotationModel = s.createDocumentModel(ANNOTATION_DOC_TYPE);
082            Comments.annotationToDocumentModel(annotation, annotationModel);
083            if (annotation instanceof ExternalEntity) {
084                annotationModel.addFacet(EXTERNAL_ENTITY_FACET);
085                Comments.externalEntityToDocumentModel((ExternalEntity) annotation, annotationModel);
086            }
087            annotationModel = Framework.getService(CommentManager.class).createComment(docToAnnotate, annotationModel);
088            return Comments.newAnnotation(annotationModel);
089        });
090    }
091
092    @Override
093    public Annotation getAnnotation(CoreSession session, String annotationId)
094            throws CommentNotFoundException, CommentSecurityException {
095        DocumentRef annotationRef = new IdRef(annotationId);
096        if (!session.exists(annotationRef)) {
097            throw new CommentNotFoundException("The document " + annotationId + " does not exist.");
098        }
099        NuxeoPrincipal principal = session.getPrincipal();
100        return CoreInstance.doPrivileged(session, s -> {
101            DocumentModel annotationModel = s.getDocument(annotationRef);
102            String parentId = (String) annotationModel.getPropertyValue(COMMENT_PARENT_ID);
103            if (!s.hasPermission(principal, new IdRef(parentId), SecurityConstants.READ)) {
104                throw new CommentSecurityException("The user " + principal.getName()
105                        + " does not have access to the annotations of document " + parentId);
106            }
107            return Comments.newAnnotation(annotationModel);
108        });
109    }
110
111    @Override
112    @SuppressWarnings("unchecked")
113    public List<Annotation> getAnnotations(CoreSession session, String documentId, String xpath)
114            throws CommentNotFoundException, CommentSecurityException {
115        DocumentRef docRef = new IdRef(documentId);
116        if (!session.exists(docRef)) {
117            throw new CommentNotFoundException("The document " + documentId + " does not exist.");
118        }
119        if (!session.hasPermission(docRef, SecurityConstants.READ)) {
120            throw new CommentSecurityException("The user " + session.getPrincipal().getName()
121                    + " does not have access to the annotations of document " + documentId);
122        }
123        DocumentModel annotatedDoc = session.getDocument(docRef);
124        CommentManager commentManager = Framework.getService(CommentManager.class);
125        if (commentManager.hasFeature(COMMENTS_LINKED_WITH_PROPERTY)) {
126            PageProviderService ppService = Framework.getService(PageProviderService.class);
127            return CoreInstance.doPrivileged(session, s -> {
128                Map<String, Serializable> props = Collections.singletonMap(CORE_SESSION_PROPERTY, (Serializable) s);
129                PageProvider<DocumentModel> pageProvider = (PageProvider<DocumentModel>) ppService.getPageProvider(
130                        GET_ANNOTATIONS_FOR_DOC_PAGEPROVIDER_NAME, null, null, null, props, documentId, xpath);
131                return pageProvider.getCurrentPage().stream().map(Comments::newAnnotation).collect(Collectors.toList());
132            });
133        }
134        return CoreInstance.doPrivileged(session, s -> {
135            return commentManager
136                    .getComments(s, annotatedDoc)
137                    .stream()
138                    .filter(annotationModel -> ANNOTATION_DOC_TYPE.equals(annotationModel.getType())
139                            && xpath.equals(annotationModel.getPropertyValue(ANNOTATION_XPATH_PROPERTY)))
140                    .map(Comments::newAnnotation)
141                    .collect(Collectors.toList());
142        });
143    }
144
145    @Override
146    public void updateAnnotation(CoreSession session, String annotationId, Annotation annotation)
147            throws CommentNotFoundException, CommentSecurityException  {
148        IdRef annotationRef = new IdRef(annotationId);
149        if (!session.exists(annotationRef)) {
150            throw new CommentNotFoundException("The annotation " + annotationId + " does not exist.");
151        }
152        NuxeoPrincipal principal = session.getPrincipal();
153        if (!principal.isAdministrator() && !annotation.getAuthor().equals(principal.getName())) {
154            throw new CommentSecurityException("The user " + principal.getName()
155                    + " can not edit annotations of document " + annotation.getParentId());
156        }
157        CoreInstance.doPrivileged(session, s -> {
158            DocumentModel annotationModel = s.getDocument(annotationRef);
159            Comments.annotationToDocumentModel(annotation, annotationModel);
160            if (annotation instanceof ExternalEntity) {
161                Comments.externalEntityToDocumentModel((ExternalEntity) annotation, annotationModel);
162            }
163            s.saveDocument(annotationModel);
164        });
165    }
166
167    @Override
168    public void deleteAnnotation(CoreSession session, String annotationId) throws CommentNotFoundException {
169        NuxeoPrincipal principal = session.getPrincipal();
170        CoreInstance.doPrivileged(session, s -> {
171            try {
172                DocumentModel annotation = s.getDocument(new IdRef(annotationId));
173                String parentId = (String) annotation.getPropertyValue(COMMENT_PARENT_ID);
174                DocumentRef parentRef = new IdRef(parentId);
175                if (!principal.isAdministrator()
176                        && !annotation.getPropertyValue(COMMENT_AUTHOR).equals(principal.getName())
177                        && !s.hasPermission(principal, parentRef, SecurityConstants.EVERYTHING)) {
178                    throw new CommentSecurityException(
179                            "The user " + principal.getName() + " can not delete annotations of document " + parentId);
180                }
181            } catch (DocumentNotFoundException e) {
182                throw new CommentNotFoundException(e);
183            }
184            Framework.getService(CommentManager.class).deleteComment(s, annotationId);
185        });
186    }
187
188    @Override
189    public Annotation getExternalAnnotation(CoreSession session, String entityId)
190            throws CommentNotFoundException, CommentSecurityException {
191        DocumentModel annotationModel = getAnnotationModel(session, entityId);
192        if (annotationModel == null) {
193            throw new CommentNotFoundException("The external annotation " + entityId + " does not exist.");
194        }
195        String parentId = (String) annotationModel.getPropertyValue(COMMENT_PARENT_ID);
196        if (!session.hasPermission(new IdRef(parentId), SecurityConstants.READ)) {
197            throw new CommentSecurityException("The user " + session.getPrincipal().getName()
198                    + " does not have access to the annotations of document " + parentId);
199        }
200        return Comments.newAnnotation(annotationModel);
201    }
202
203    @Override
204    public void updateExternalAnnotation(CoreSession session, String entityId, Annotation annotation)
205            throws CommentNotFoundException, CommentSecurityException {
206        NuxeoPrincipal principal = session.getPrincipal();
207        if (!principal.isAdministrator() && !annotation.getAuthor().equals(principal.getName())) {
208            throw new CommentSecurityException("The user " + session.getPrincipal().getName()
209                    + " can not edit annotations of document " + annotation.getParentId());
210        }
211        DocumentModel annotationModel = getAnnotationModel(session, entityId);
212        if (annotationModel == null) {
213            throw new CommentNotFoundException("The external annotation " + entityId + " does not exist.");
214        }
215        Comments.annotationToDocumentModel(annotation, annotationModel);
216        if (annotation instanceof ExternalEntity) {
217            Comments.externalEntityToDocumentModel((ExternalEntity) annotation, annotationModel);
218        }
219        session.saveDocument(annotationModel);
220    }
221
222    @Override
223    public void deleteExternalAnnotation(CoreSession session, String entityId)
224            throws CommentNotFoundException, CommentSecurityException {
225        DocumentModel annotationModel = getAnnotationModel(session, entityId);
226        if (annotationModel == null) {
227            throw new CommentNotFoundException("The external annotation " + entityId + " does not exist.");
228        }
229        NuxeoPrincipal principal = session.getPrincipal();
230        String parentId = (String) annotationModel.getPropertyValue(COMMENT_PARENT_ID);
231        if (!principal.isAdministrator()
232                && !annotationModel.getPropertyValue(COMMENT_AUTHOR).equals(principal.getName())
233                && !session.hasPermission(new IdRef(parentId), SecurityConstants.EVERYTHING)) {
234            throw new CommentSecurityException(
235                    "The user " + principal.getName() + " can not delete annotations of document " + parentId);
236        }
237        Framework.getService(CommentManager.class).deleteComment(session, annotationModel.getId());
238    }
239
240    @SuppressWarnings("unchecked")
241    protected DocumentModel getAnnotationModel(CoreSession session, String entityId) {
242        PageProviderService ppService = Framework.getService(PageProviderService.class);
243        Map<String, Serializable> props = singletonMap(CORE_SESSION_PROPERTY, (Serializable) session);
244        List<DocumentModel> results = ((PageProvider<DocumentModel>) ppService.getPageProvider(
245                GET_ANNOTATION_PAGEPROVIDER_NAME, null, 1L, 0L, props, entityId)).getCurrentPage();
246        if (results.isEmpty()) {
247            return null;
248        }
249        return results.get(0);
250    }
251
252}