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 org.nuxeo.ecm.platform.comment.api.CommentConstants.COMMENT_PARENT_ID_PROPERTY; 024 025import java.util.Collection; 026import java.util.List; 027import java.util.function.Function; 028import java.util.stream.Collectors; 029import java.util.stream.Stream; 030 031import org.nuxeo.ecm.core.api.CoreInstance; 032import org.nuxeo.ecm.core.api.CoreSession; 033import org.nuxeo.ecm.core.api.DocumentModel; 034import org.nuxeo.ecm.core.api.DocumentRef; 035import org.nuxeo.ecm.core.api.IdRef; 036import org.nuxeo.ecm.core.api.PartialList; 037import org.nuxeo.ecm.platform.comment.api.Comment; 038import org.nuxeo.ecm.platform.comment.api.CommentConstants; 039import org.nuxeo.ecm.platform.comment.api.CommentManager; 040import org.nuxeo.ecm.platform.comment.api.exceptions.CommentNotFoundException; 041import org.nuxeo.ecm.platform.comment.api.exceptions.CommentSecurityException; 042 043/** 044 * @since 10.3 045 */ 046public class BridgeCommentManager extends AbstractCommentManager { 047 048 protected final CommentManager first; 049 050 protected final CommentManager second; 051 052 public BridgeCommentManager(CommentManager first, CommentManager second) { 053 this.first = first; 054 this.second = second; 055 } 056 057 /** 058 * @since 11.1 059 */ 060 public CommentManager getFirst() { 061 return first; 062 } 063 064 /** 065 * @since 11.1 066 */ 067 public CommentManager getSecond() { 068 return second; 069 } 070 071 @Override 072 public List<DocumentModel> getComments(DocumentModel docModel) { 073 return Stream.concat(first.getComments(docModel).stream(), second.getComments(docModel).stream()) 074 .distinct() 075 .collect(Collectors.toList()); 076 } 077 078 @Override 079 public List<DocumentModel> getComments(CoreSession session, DocumentModel docModel) 080 throws CommentSecurityException { 081 return Stream.concat(first.getComments(session, docModel).stream(), 082 second.getComments(session, docModel).stream()).distinct().collect(Collectors.toList()); 083 } 084 085 @Override 086 @SuppressWarnings("deprecation") 087 public DocumentModel createComment(DocumentModel docModel, String comment) { 088 return second.createComment(docModel, comment); 089 } 090 091 @Override 092 @SuppressWarnings("removal") 093 public DocumentModel createComment(DocumentModel docModel, String comment, String author) { 094 return second.createComment(docModel, comment, author); 095 } 096 097 @Override 098 public DocumentModel createComment(DocumentModel docModel, DocumentModel comment) throws CommentSecurityException { 099 return second.createComment(docModel, comment); 100 } 101 102 @Override 103 @SuppressWarnings("removal") 104 public DocumentModel createComment(DocumentModel docModel, DocumentModel parent, DocumentModel child) { 105 return second.createComment(docModel, parent, child); 106 } 107 108 @Override 109 @SuppressWarnings("removal") 110 public void deleteComment(DocumentModel docModel, DocumentModel comment) { 111 if (comment.getPropertyValue(COMMENT_PARENT_ID_PROPERTY) != null) { 112 second.deleteComment(docModel, comment); 113 } else { 114 first.deleteComment(docModel, comment); 115 } 116 } 117 118 @Override 119 @SuppressWarnings("removal") 120 public List<DocumentModel> getDocumentsForComment(DocumentModel comment) { 121 return Stream.concat(first.getDocumentsForComment(comment).stream(), 122 second.getDocumentsForComment(comment).stream()).distinct().collect(Collectors.toList()); 123 } 124 125 @Override 126 public DocumentModel getThreadForComment(DocumentModel comment) throws CommentSecurityException { 127 // handle only Relation to Property bridge, this method should have been deprecated in 10.10 due to unused 128 if (comment.getPropertyValue(COMMENT_PARENT_ID_PROPERTY) != null) { 129 if (second instanceof TreeCommentManager) { 130 return first.getThreadForComment(comment); 131 } else { 132 return second.getThreadForComment(comment); 133 } 134 } 135 return first.getThreadForComment(comment); 136 } 137 138 @Override 139 public DocumentModel createLocatedComment(DocumentModel docModel, DocumentModel comment, String path) 140 throws CommentSecurityException { 141 return second.createLocatedComment(docModel, comment, path); 142 } 143 144 @Override 145 public Comment createComment(CoreSession session, Comment comment) 146 throws CommentNotFoundException, CommentSecurityException { 147 return second.createComment(session, comment); 148 } 149 150 @Override 151 public Comment getComment(CoreSession session, String commentId) 152 throws CommentNotFoundException, CommentSecurityException { 153 return second.getComment(session, commentId); 154 } 155 156 @Override 157 public List<Comment> getComments(CoreSession session, String documentId) { 158 return Stream.concat(first.getComments(session, documentId).stream(), 159 second.getComments(session, documentId).stream()).distinct().collect(Collectors.toList()); 160 } 161 162 @Override 163 public PartialList<Comment> getComments(CoreSession session, String documentId, Long pageSize, 164 Long currentPageIndex, boolean sortAscending) throws CommentSecurityException { 165 List<Comment> firstComments = first.getComments(session, documentId, pageSize, currentPageIndex, sortAscending); 166 List<Comment> secondComments = second.getComments(session, documentId, pageSize, currentPageIndex, 167 sortAscending); 168 List<Comment> allComments = Stream.concat(firstComments.stream(), secondComments.stream()) 169 .distinct() 170 .collect(Collectors.toList()); 171 return new PartialList<>(allComments, allComments.size()); 172 } 173 174 @Override 175 public List<Comment> getComments(CoreSession session, Collection<String> documentIds) { 176 List<Comment> firstComments = first.getComments(session, documentIds); 177 List<Comment> secondComments = second.getComments(session, documentIds); 178 return Stream.concat(firstComments.stream(), secondComments.stream()).distinct().collect(Collectors.toList()); 179 } 180 181 @Override 182 public Comment updateComment(CoreSession session, String commentId, Comment comment) 183 throws CommentNotFoundException, CommentSecurityException { 184 DocumentRef commentRef = new IdRef(commentId); 185 return CoreInstance.doPrivileged(session, s -> { 186 // retrieve comment to check which service handles it 187 if (!s.exists(commentRef)) { 188 throw new CommentNotFoundException("The comment " + commentId + " does not exist"); 189 } 190 if (s.getDocument(commentRef).getPropertyValue(COMMENT_PARENT_ID_PROPERTY) != null) { 191 return second.updateComment(session, commentId, comment); 192 } else { 193 return first.updateComment(session, commentId, comment); 194 } 195 }); 196 } 197 198 @Override 199 public void deleteComment(CoreSession session, String commentId) 200 throws CommentNotFoundException, CommentSecurityException { 201 DocumentRef commentRef = new IdRef(commentId); 202 CoreInstance.doPrivileged(session, s -> { 203 // retrieve comment to check which service handles it 204 if (!s.exists(commentRef)) { 205 throw new CommentNotFoundException("The comment " + commentId + " does not exist"); 206 } 207 if (s.getDocument(commentRef).getPropertyValue(COMMENT_PARENT_ID_PROPERTY) != null) { 208 second.deleteComment(session, commentId); 209 } else { 210 first.deleteComment(session, commentId); 211 } 212 }); 213 } 214 215 @Override 216 public Comment getExternalComment(CoreSession session, String documentId, String entityId) 217 throws CommentNotFoundException, CommentSecurityException { 218 return second.getExternalComment(session, documentId, entityId); 219 } 220 221 @Override 222 public Comment updateExternalComment(CoreSession session, String documentId, String entityId, Comment comment) 223 throws CommentNotFoundException, CommentSecurityException { 224 return second.updateExternalComment(session, documentId, entityId, comment); 225 } 226 227 @Override 228 public void deleteExternalComment(CoreSession session, String documentId, String entityId) 229 throws CommentNotFoundException, CommentSecurityException { 230 second.deleteExternalComment(session, documentId, entityId); 231 } 232 233 @Override 234 public boolean hasFeature(Feature feature) { 235 switch (feature) { 236 case COMMENTS_LINKED_WITH_PROPERTY: 237 return false; 238 default: 239 throw new UnsupportedOperationException(feature.name()); 240 } 241 } 242 243 @Override 244 public DocumentRef getTopLevelDocumentRef(CoreSession session, DocumentRef commentIdRef) { 245 return execute(session, commentIdRef, cm -> cm.getTopLevelDocumentRef(session, commentIdRef)); 246 } 247 248 @Override 249 protected DocumentModel getTopLevelDocument(CoreSession session, DocumentModel commentDoc) { 250 // this abstract method should be called within an implementation - no need to implement it in the bridge 251 throw new UnsupportedOperationException(); 252 } 253 254 @Override 255 protected DocumentModel getCommentedDocument(CoreSession session, DocumentModel commentDoc) { 256 // this abstract method should be called within an implementation - no need to implement it in the bridge 257 throw new UnsupportedOperationException(); 258 } 259 260 /** 261 * Executes the given function for a comment document ref, depending on the types of comment managers. 262 * <p> 263 * In some cases, leveraging {@link CommentConstants#COMMENT_PARENT_ID_PROPERTY} is not enough, this is the case 264 * when bridge is used with {@link PropertyCommentManager} and {@link TreeCommentManager}. 265 * <ul> 266 * <li>{@link CommentManagerImpl} (or RelationCommentManager): Comments structures are stored in jenaGraph</li> 267 * <li>{@link PropertyCommentManager}: All comments are stored under a hidden folder and each comment stores its 268 * parent id in {@code comment:parentId} property</li> 269 * <li>{@link TreeCommentManager}: A {@link CommentConstants#COMMENT_ROOT_DOC_TYPE} document is created under the 270 * top level document to store the comments. Replies are then stored directly under their parent (which is a 271 * comment)</li> 272 * </ul> 273 * 274 * @since 11.1 275 */ 276 @SuppressWarnings("deprecation") 277 protected <T> T execute(CoreSession s, DocumentRef documentRef, Function<CommentManager, T> function) { 278 return CoreInstance.doPrivileged(s, session -> { 279 DocumentModel documentModel = session.getDocument(documentRef); 280 281 // From `Relation` to `Property` 282 if (first instanceof CommentManagerImpl && second instanceof PropertyCommentManager) { 283 if (documentModel.getPropertyValue(COMMENT_PARENT_ID_PROPERTY) != null) { 284 // Comment is in property model 285 return function.apply(second); 286 } 287 // Comment still in relation model 288 return function.apply(first); 289 } 290 291 // From `Property` to `Tree` 292 if (first instanceof PropertyCommentManager && second instanceof TreeCommentManager) { 293 // In this case we cannot just rely on `comment:parentId` but on the type of doc that contains the 294 // comment 295 DocumentRef parentRef = documentModel.getParentRef(); 296 // Comment is under property model 297 if (session.getDocument(parentRef).getType().equals("HiddenFolder")) { 298 return function.apply(first); 299 } 300 // Comment is under tree model 301 return function.apply(second); 302 } 303 304 throw new IllegalArgumentException(String.format( 305 "Undefined behaviour for document ref: %s, first: %s, second: %s ", documentModel, first, second)); 306 }); 307 } 308 309}