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.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.CommentConstants.COMMENT_SCHEMA;
027import static org.nuxeo.ecm.platform.comment.api.CommentManager.Feature.COMMENTS_ARE_SPECIAL_CHILDREN;
028import static org.nuxeo.ecm.platform.comment.api.CommentManager.Feature.COMMENTS_LINKED_WITH_PROPERTY;
029import static org.nuxeo.ecm.platform.comment.impl.AbstractCommentManager.COMMENTS_DIRECTORY;
030import static org.nuxeo.ecm.platform.query.nxql.CoreQueryAndFetchPageProvider.CORE_SESSION_PROPERTY;
031
032import java.io.Serializable;
033import java.util.List;
034import java.util.Map;
035import java.util.stream.Collectors;
036import java.util.stream.Stream;
037
038import org.nuxeo.ecm.core.api.CoreInstance;
039import org.nuxeo.ecm.core.api.CoreSession;
040import org.nuxeo.ecm.core.api.DocumentModel;
041import org.nuxeo.ecm.core.api.DocumentNotFoundException;
042import org.nuxeo.ecm.core.api.DocumentRef;
043import org.nuxeo.ecm.core.api.IdRef;
044import org.nuxeo.ecm.core.api.security.SecurityConstants;
045import org.nuxeo.ecm.platform.comment.api.Annotation;
046import org.nuxeo.ecm.platform.comment.api.AnnotationService;
047import org.nuxeo.ecm.platform.comment.api.CommentManager;
048import org.nuxeo.ecm.platform.comment.api.exceptions.CommentNotFoundException;
049import org.nuxeo.ecm.platform.comment.api.exceptions.CommentSecurityException;
050import org.nuxeo.ecm.platform.query.api.PageProvider;
051import org.nuxeo.ecm.platform.query.api.PageProviderService;
052import org.nuxeo.runtime.api.Framework;
053import org.nuxeo.runtime.model.DefaultComponent;
054
055/**
056 * @since 10.1
057 */
058public class AnnotationServiceImpl extends DefaultComponent implements AnnotationService {
059
060    /** @deprecated since 11.1, because unused. */
061    @Deprecated(since = "11.1")
062    protected static final String GET_ANNOTATION_PAGEPROVIDER_NAME = "GET_ANNOTATION_AS_EXTERNAL_ENTITY";
063
064    /** @deprecated since 11.1, because unused. */
065    @Deprecated(since = "11.1")
066    @SuppressWarnings("DeprecatedIsStillUsed")
067    protected static final String GET_ANNOTATIONS_FOR_DOC_PAGEPROVIDER_NAME = "GET_ANNOTATIONS_FOR_DOCUMENT";
068
069    /** @since 11.1 */
070    protected static final String GET_ANNOTATIONS_FOR_DOCUMENT_PAGE_PROVIDER_NAME = "GET_ANNOTATIONS_FOR_DOCUMENT_BY_ECM_PARENT";
071
072    @Override
073    public Annotation createAnnotation(CoreSession session, Annotation annotation) throws CommentSecurityException {
074        return (Annotation) Framework.getService(CommentManager.class).createComment(session, annotation);
075    }
076
077    @Override
078    public Annotation getAnnotation(CoreSession s, String annotationId)
079            throws CommentNotFoundException, CommentSecurityException {
080        return (Annotation) Framework.getService(CommentManager.class).getComment(s, annotationId);
081    }
082
083    @Override
084    public List<Annotation> getAnnotations(CoreSession session, String documentId, String xpath)
085            throws CommentNotFoundException, CommentSecurityException {
086        DocumentRef docRef = new IdRef(documentId);
087        try {
088            if (!session.hasPermission(docRef, SecurityConstants.READ)) {
089                throw new CommentSecurityException("The user " + session.getPrincipal().getName()
090                        + " does not have access to the annotations of document " + documentId);
091            }
092        } catch (DocumentNotFoundException dnfe) {
093            throw new CommentNotFoundException(String.format("The document %s does not exist.", docRef), dnfe);
094        }
095        return streamAnnotations(session, documentId, xpath).map(doc -> doc.getAdapter(Annotation.class))
096                                                            .collect(Collectors.toList());
097    }
098
099    protected Stream<DocumentModel> streamAnnotations(CoreSession session, String documentId, String xpath) {
100        DocumentModel annotatedDoc = session.getDocument(new IdRef(documentId));
101        CommentManager commentManager = Framework.getService(CommentManager.class);
102        return CoreInstance.doPrivileged(session, s -> {
103            if (commentManager.hasFeature(COMMENTS_LINKED_WITH_PROPERTY)) {
104                Map<String, Serializable> props = Map.of(CORE_SESSION_PROPERTY, (Serializable) s);
105                List<DocumentModel> docs = getPageProviderPage(GET_ANNOTATIONS_FOR_DOC_PAGEPROVIDER_NAME, props, documentId, xpath);
106                docs.forEach(doc -> doc.detach(true)); // due to privileged session
107                return docs;
108            } else if (commentManager.hasFeature(COMMENTS_ARE_SPECIAL_CHILDREN)) {
109                // handle first comment/reply cases
110                String parentId = documentId;
111                if (!annotatedDoc.hasSchema(COMMENT_SCHEMA) && s.hasChild(annotatedDoc.getRef(), COMMENTS_DIRECTORY)) {
112                    DocumentModel commentsFolder = s.getChild(annotatedDoc.getRef(), COMMENTS_DIRECTORY);
113                    parentId = commentsFolder.getId();
114                }
115                // when comments are special children we can leverage inherited acls
116                Map<String, Serializable> props = Map.of(CORE_SESSION_PROPERTY, (Serializable) session);
117                return getPageProviderPage(GET_ANNOTATIONS_FOR_DOCUMENT_PAGE_PROVIDER_NAME, props, parentId, xpath);
118            }
119            // provide a poor support for deprecated implementations
120            return commentManager.getComments(session, annotatedDoc)
121                                 .stream()
122                                 .filter(annotationModel -> ANNOTATION_DOC_TYPE.equals(annotationModel.getType())
123                                         && xpath.equals(annotationModel.getPropertyValue(ANNOTATION_XPATH_PROPERTY)))
124                                 .collect(Collectors.toList());
125        }).stream();
126    }
127
128    @SuppressWarnings("unchecked")
129    protected List<DocumentModel> getPageProviderPage(String ppName, Map<String, Serializable> props,
130            Object... parameters) {
131        var ppService = Framework.getService(PageProviderService.class);
132        var pageProvider = (PageProvider<DocumentModel>) ppService.getPageProvider(ppName, null, null, null, props,
133                parameters);
134        return pageProvider.getCurrentPage();
135    }
136
137    @Override
138    public void updateAnnotation(CoreSession session, String annotationId, Annotation annotation)
139            throws CommentNotFoundException, CommentSecurityException {
140        Framework.getService(CommentManager.class).updateComment(session, annotationId, annotation);
141    }
142
143    @Override
144    public void deleteAnnotation(CoreSession session, String annotationId) throws CommentNotFoundException {
145        Framework.getService(CommentManager.class).deleteComment(session, annotationId);
146    }
147
148    @Override
149    public Annotation getExternalAnnotation(CoreSession session, String documentId, String entityId)
150            throws CommentNotFoundException, CommentSecurityException {
151        return (Annotation) Framework.getService(CommentManager.class)
152                                     .getExternalComment(session, documentId, entityId);
153    }
154
155    @Override
156    public Annotation updateExternalAnnotation(CoreSession session, String documentId, String entityId,
157            Annotation annotation) throws CommentNotFoundException, CommentSecurityException {
158        return (Annotation) Framework.getService(CommentManager.class)
159                                     .updateExternalComment(session, documentId, entityId, annotation);
160    }
161
162    @Override
163    public void deleteExternalAnnotation(CoreSession session, String documentId, String entityId)
164            throws CommentNotFoundException, CommentSecurityException {
165        Framework.getService(CommentManager.class).deleteExternalComment(session, documentId, entityId);
166    }
167
168    /**
169     * @deprecated since 11.1. No used any more.
170     */
171    @SuppressWarnings("unchecked")
172    @Deprecated(since = "11.1", forRemoval = true)
173    protected DocumentModel getAnnotationModel(CoreSession session, String entityId) {
174        PageProviderService ppService = Framework.getService(PageProviderService.class);
175        Map<String, Serializable> props = singletonMap(CORE_SESSION_PROPERTY, (Serializable) session);
176        List<DocumentModel> results = ((PageProvider<DocumentModel>) ppService.getPageProvider(
177                GET_ANNOTATION_PAGEPROVIDER_NAME, null, 1L, 0L, props, entityId)).getCurrentPage();
178        if (results.isEmpty()) {
179            return null;
180        }
181        return results.get(0);
182    }
183
184}