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 *
019 */
020
021package org.nuxeo.ecm.annotation;
022
023import static org.nuxeo.ecm.annotation.AnnotationConstants.ANNOTATION_DOCUMENT_ID_PROPERTY;
024import static org.nuxeo.ecm.annotation.AnnotationConstants.ANNOTATION_DOC_TYPE;
025import static org.nuxeo.ecm.annotation.AnnotationConstants.ANNOTATION_ENTITY_PROPERTY;
026import static org.nuxeo.ecm.annotation.AnnotationConstants.ANNOTATION_ID_PROPERTY;
027import static org.nuxeo.ecm.annotation.AnnotationConstants.ANNOTATION_XPATH_PROPERTY;
028
029import java.io.Serializable;
030import java.util.Collections;
031import java.util.List;
032import java.util.Map;
033import java.util.stream.Collectors;
034
035import org.apache.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
037import org.nuxeo.ecm.core.api.CoreInstance;
038import org.nuxeo.ecm.core.api.CoreSession;
039import org.nuxeo.ecm.core.api.DocumentModel;
040import org.nuxeo.ecm.core.api.IdRef;
041import org.nuxeo.ecm.core.api.PathRef;
042import org.nuxeo.ecm.platform.query.api.PageProvider;
043import org.nuxeo.ecm.platform.query.api.PageProviderService;
044import org.nuxeo.ecm.platform.query.nxql.CoreQueryAndFetchPageProvider;
045import org.nuxeo.runtime.api.Framework;
046import org.nuxeo.runtime.model.DefaultComponent;
047import org.nuxeo.runtime.services.config.ConfigurationService;
048
049/**
050 * @since 10.1
051 */
052public class AnnotationServiceImpl extends DefaultComponent implements AnnotationService {
053
054    private static final Log log = LogFactory.getLog(AnnotationServiceImpl.class);
055
056    protected static final String ANNOTATION_NAME = "annotation";
057
058    protected static final String GET_ANNOTATION_PAGEPROVIDER_NAME = "GET_ANNOTATION";
059
060    protected static final String GET_ANNOTATIONS_FOR_DOC_PAGEPROVIDER_NAME = "GET_ANNOTATIONS_FOR_DOCUMENT";
061
062    protected static final String ANNOTATIONS_PLACELESS_STORAGE_PROPERTY = "nuxeo.annotations.placeless.storage";
063
064    protected static final String HIDDEN_FOLDER_TYPE = "HiddenFolder";
065
066    protected static final String ANNOTATION_FOLDER_NAME = "Annotations";
067
068    @Override
069    public Annotation createAnnotation(CoreSession session, Annotation annotation) {
070
071        ConfigurationService configurationService = Framework.getService(ConfigurationService.class);
072        boolean annotationPlaceless = configurationService.isBooleanPropertyTrue(
073                ANNOTATIONS_PLACELESS_STORAGE_PROPERTY);
074
075        return CoreInstance.doPrivileged(session, s -> {
076            String path = null;
077            if (!annotationPlaceless) {
078                // Create or retrieve the folder to store the annotation.
079                // If the document is under a domain, the folder is a child of this domain.
080                // Otherwise, it is a child of the root document.
081                DocumentModel annotatedDoc = s.getDocument(new IdRef(annotation.getDocumentId()));
082                String parentPath = s.getRootDocument().getPathAsString();
083                if (annotatedDoc.getPath().segmentCount() > 1) {
084                    parentPath += annotatedDoc.getPath().segment(0);
085                }
086                PathRef ref = new PathRef(parentPath, ANNOTATION_FOLDER_NAME);
087                DocumentModel annotationFolderDoc = s.createDocumentModel(parentPath, ANNOTATION_FOLDER_NAME,
088                        HIDDEN_FOLDER_TYPE);
089                s.getOrCreateDocument(annotationFolderDoc);
090                s.save();
091                path = ref.toString();
092            }
093            DocumentModel annotationModel = s.createDocumentModel(path, ANNOTATION_NAME, ANNOTATION_DOC_TYPE);
094            setAnnotationProperties(annotationModel, annotation);
095            annotationModel = s.createDocument(annotationModel);
096            return new AnnotationImpl(annotationModel);
097        });
098    }
099
100    @Override
101    public Annotation getAnnotation(CoreSession session, String annotationId) {
102        return CoreInstance.doPrivileged(session, s -> {
103            DocumentModel annotationModel = getAnnotationModel(s, annotationId);
104            if (annotationModel == null) {
105                return null;
106            }
107            return new AnnotationImpl(annotationModel);
108        });
109    }
110
111    @Override
112    @SuppressWarnings("unchecked")
113    public List<Annotation> getAnnotations(CoreSession session, String documentId, String xpath) {
114        return CoreInstance.doPrivileged(session, s -> {
115            PageProviderService ppService = Framework.getService(PageProviderService.class);
116            Map<String, Serializable> props = Collections.singletonMap(
117                    CoreQueryAndFetchPageProvider.CORE_SESSION_PROPERTY, (Serializable) s);
118            List<DocumentModel> annotationList = //
119                    ((PageProvider<DocumentModel>) ppService.getPageProvider(GET_ANNOTATIONS_FOR_DOC_PAGEPROVIDER_NAME,
120                            null, null, null, props, documentId, xpath)).getCurrentPage();
121            return annotationList.stream().map(AnnotationImpl::new).collect(Collectors.toList());
122        });
123    }
124
125    @Override
126    public void updateAnnotation(CoreSession session, Annotation annotation) {
127        CoreInstance.doPrivileged(session, s -> {
128            DocumentModel annotationModel = getAnnotationModel(s, annotation.getId());
129            if (annotationModel == null) {
130                if (log.isWarnEnabled()) {
131                    log.warn("The annotation " + annotation.getId() + " on document blob " + annotation.getXpath()
132                            + " does not exist. Update operation is ignored.");
133                }
134                return;
135            }
136            setAnnotationProperties(annotationModel, annotation);
137            s.saveDocument(annotationModel);
138        });
139    }
140
141    @Override
142    public void deleteAnnotation(CoreSession session, String annotationId) throws IllegalArgumentException {
143        CoreInstance.doPrivileged(session, s -> {
144            DocumentModel annotationModel = getAnnotationModel(s, annotationId);
145            if (annotationModel == null) {
146                throw new IllegalArgumentException("The annotation " + annotationId + " does not exist.");
147            }
148            s.removeDocument(annotationModel.getRef());
149        });
150    }
151
152    protected void setAnnotationProperties(DocumentModel annotationModel, Annotation annotation) {
153        annotationModel.setPropertyValue(ANNOTATION_ID_PROPERTY, annotation.getId());
154        annotationModel.setPropertyValue(ANNOTATION_DOCUMENT_ID_PROPERTY, annotation.getDocumentId());
155        annotationModel.setPropertyValue(ANNOTATION_XPATH_PROPERTY, annotation.getXpath());
156        annotationModel.setPropertyValue(ANNOTATION_ENTITY_PROPERTY, annotation.getEntity());
157    }
158
159    /**
160     * Session must be privileged.
161     */
162    @SuppressWarnings("unchecked")
163    protected DocumentModel getAnnotationModel(CoreSession session, String annotationId) {
164        PageProviderService ppService = Framework.getService(PageProviderService.class);
165        Map<String, Serializable> props = Collections.singletonMap(CoreQueryAndFetchPageProvider.CORE_SESSION_PROPERTY,
166                (Serializable) session);
167        List<DocumentModel> results = ((PageProvider<DocumentModel>) ppService.getPageProvider(
168                GET_ANNOTATION_PAGEPROVIDER_NAME, null, null, null, props, annotationId)).getCurrentPage();
169        if (results.isEmpty()) {
170            return null;
171        }
172        return results.get(0);
173    }
174
175}