001/* 002 * (C) Copyright 2012 Nuxeo SA (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 * Olivier Grisel <ogrisel@nuxeo.com> 018 * Antoine Taillefer <ataillefer@nuxeo.com> 019 */ 020package org.nuxeo.drive.service.impl; 021 022import static org.nuxeo.ecm.platform.query.nxql.CoreQueryDocumentPageProvider.CORE_SESSION_PROPERTY; 023 024import java.io.Serializable; 025import java.util.ArrayList; 026import java.util.Calendar; 027import java.util.Collections; 028import java.util.HashMap; 029import java.util.HashSet; 030import java.util.LinkedHashSet; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034import java.util.TimeZone; 035import java.util.TreeSet; 036 037import org.apache.logging.log4j.LogManager; 038import org.apache.logging.log4j.Logger; 039import org.nuxeo.common.utils.Path; 040import org.nuxeo.drive.service.FileSystemChangeFinder; 041import org.nuxeo.drive.service.FileSystemChangeSummary; 042import org.nuxeo.drive.service.FileSystemItemChange; 043import org.nuxeo.drive.service.NuxeoDriveEvents; 044import org.nuxeo.drive.service.NuxeoDriveManager; 045import org.nuxeo.drive.service.SynchronizationRoots; 046import org.nuxeo.drive.service.TooManyChangesException; 047import org.nuxeo.ecm.collections.api.CollectionConstants; 048import org.nuxeo.ecm.collections.api.CollectionManager; 049import org.nuxeo.ecm.core.api.CloseableCoreSession; 050import org.nuxeo.ecm.core.api.CoreInstance; 051import org.nuxeo.ecm.core.api.CoreSession; 052import org.nuxeo.ecm.core.api.DocumentModel; 053import org.nuxeo.ecm.core.api.DocumentNotFoundException; 054import org.nuxeo.ecm.core.api.DocumentRef; 055import org.nuxeo.ecm.core.api.DocumentSecurityException; 056import org.nuxeo.ecm.core.api.IdRef; 057import org.nuxeo.ecm.core.api.IterableQueryResult; 058import org.nuxeo.ecm.core.api.NuxeoException; 059import org.nuxeo.ecm.core.api.NuxeoPrincipal; 060import org.nuxeo.ecm.core.api.PathRef; 061import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; 062import org.nuxeo.ecm.core.api.event.CoreEventConstants; 063import org.nuxeo.ecm.core.api.repository.RepositoryManager; 064import org.nuxeo.ecm.core.api.security.SecurityConstants; 065import org.nuxeo.ecm.core.cache.Cache; 066import org.nuxeo.ecm.core.cache.CacheService; 067import org.nuxeo.ecm.core.event.Event; 068import org.nuxeo.ecm.core.event.EventService; 069import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 070import org.nuxeo.ecm.core.query.sql.NXQL; 071import org.nuxeo.ecm.platform.audit.service.NXAuditEventsService; 072import org.nuxeo.ecm.platform.ec.notification.NotificationConstants; 073import org.nuxeo.ecm.platform.query.api.PageProvider; 074import org.nuxeo.ecm.platform.query.api.PageProviderService; 075import org.nuxeo.ecm.platform.query.nxql.NXQLQueryBuilder; 076import org.nuxeo.runtime.api.Framework; 077import org.nuxeo.runtime.model.ComponentContext; 078import org.nuxeo.runtime.model.ComponentInstance; 079import org.nuxeo.runtime.model.DefaultComponent; 080 081/** 082 * Manage list of NuxeoDrive synchronization roots and devices for a given nuxeo user. 083 */ 084public class NuxeoDriveManagerImpl extends DefaultComponent implements NuxeoDriveManager { 085 086 private static final Logger log = LogManager.getLogger(NuxeoDriveManagerImpl.class); 087 088 public static final String CHANGE_FINDER_EP = "changeFinder"; 089 090 public static final String NUXEO_DRIVE_FACET = "DriveSynchronized"; 091 092 public static final String DRIVE_SUBSCRIPTIONS_PROPERTY = "drv:subscriptions"; 093 094 public static final String DOCUMENT_CHANGE_LIMIT_PROPERTY = "org.nuxeo.drive.document.change.limit"; 095 096 public static final TimeZone UTC = TimeZone.getTimeZone("UTC"); 097 098 public static final String DRIVE_SYNC_ROOT_CACHE = "driveSyncRoot"; 099 100 public static final String DRIVE_COLLECTION_SYNC_ROOT__MEMBER_CACHE = "driveCollectionSyncRootMember"; 101 102 protected static final long COLLECTION_CONTENT_PAGE_SIZE = 1000L; 103 104 /** 105 * Cache holding the synchronization roots for a given user (first map key) and repository (second map key). 106 */ 107 protected Cache syncRootCache; 108 109 /** 110 * Cache holding the collection sync root member ids for a given user (first map key) and repository (second map 111 * key). 112 */ 113 protected Cache collectionSyncRootMemberCache; 114 115 protected static ChangeFinderRegistry changeFinderRegistry; 116 117 protected FileSystemChangeFinder changeFinder; 118 119 protected Cache getSyncRootCache() { 120 return syncRootCache; 121 } 122 123 protected Cache getCollectionSyncRootMemberCache() { 124 return collectionSyncRootMemberCache; 125 } 126 127 protected void clearCache() { 128 log.debug("Invalidating synchronization root cache and collection sync root member cache for all users"); 129 syncRootCache.invalidateAll(); 130 collectionSyncRootMemberCache.invalidateAll(); 131 } 132 133 @Override 134 public void invalidateSynchronizationRootsCache(String userName) { 135 log.debug("Invalidating synchronization root cache for user: {}", userName); 136 getSyncRootCache().invalidate(userName); 137 } 138 139 @Override 140 public void invalidateCollectionSyncRootMemberCache(String userName) { 141 log.debug("Invalidating collection sync root member cache for user: {}", userName); 142 getCollectionSyncRootMemberCache().invalidate(userName); 143 } 144 145 @Override 146 public void invalidateCollectionSyncRootMemberCache() { 147 log.debug("Invalidating collection sync root member cache for all users"); 148 getCollectionSyncRootMemberCache().invalidateAll(); 149 } 150 151 @Override 152 public void registerSynchronizationRoot(NuxeoPrincipal principal, final DocumentModel newRootContainer, 153 CoreSession session) { 154 final String userName = principal.getName(); 155 log.debug("Registering synchronization root {} for {}", newRootContainer, userName); 156 157 // If new root is child of a sync root, ignore registration, except for 158 // the 'Locally Edited' collection: it is under the personal workspace 159 // and we want to allow both the personal workspace and the 'Locally 160 // Edited' collection to be registered as sync roots 161 Map<String, SynchronizationRoots> syncRoots = getSynchronizationRoots(principal); 162 SynchronizationRoots synchronizationRoots = syncRoots.get(session.getRepositoryName()); 163 if (!NuxeoDriveManager.LOCALLY_EDITED_COLLECTION_NAME.equals(newRootContainer.getName())) { 164 for (String syncRootPath : synchronizationRoots.getPaths()) { 165 String syncRootPrefixedPath = syncRootPath + "/"; 166 167 if (newRootContainer.getPathAsString().startsWith(syncRootPrefixedPath)) { 168 // the only exception is when the right inheritance is 169 // blocked 170 // in the hierarchy 171 boolean rightInheritanceBlockedInTheHierarchy = false; 172 // should get only parents up to the sync root 173 174 Path parentPath = newRootContainer.getPath().removeLastSegments(1); 175 while (!"/".equals(parentPath.toString())) { 176 String parentPathAsString = parentPath.toString() + "/"; 177 if (!parentPathAsString.startsWith(syncRootPrefixedPath)) { 178 break; 179 } 180 PathRef parentRef = new PathRef(parentPathAsString); 181 if (!session.hasPermission(principal, parentRef, SecurityConstants.READ)) { 182 rightInheritanceBlockedInTheHierarchy = true; 183 break; 184 } 185 parentPath = parentPath.removeLastSegments(1); 186 } 187 if (!rightInheritanceBlockedInTheHierarchy) { 188 return; 189 } 190 } 191 } 192 } 193 194 checkCanUpdateSynchronizationRoot(newRootContainer, session); 195 196 // Unregister any sub-folder of the new root, except for the 'Locally 197 // Edited' collection 198 String newRootPrefixedPath = newRootContainer.getPathAsString() + "/"; 199 for (String existingRootPath : synchronizationRoots.getPaths()) { 200 if (!existingRootPath.endsWith(NuxeoDriveManager.LOCALLY_EDITED_COLLECTION_NAME)) { 201 if (existingRootPath.startsWith(newRootPrefixedPath)) { 202 // Unregister the nested root sub-folder first 203 PathRef ref = new PathRef(existingRootPath); 204 if (session.exists(ref)) { 205 DocumentModel subFolder = session.getDocument(ref); 206 unregisterSynchronizationRoot(principal, subFolder, session); 207 } 208 } 209 } 210 } 211 212 UnrestrictedSessionRunner runner = new UnrestrictedSessionRunner(session) { 213 @Override 214 public void run() { 215 if (!newRootContainer.hasFacet(NUXEO_DRIVE_FACET)) { 216 newRootContainer.addFacet(NUXEO_DRIVE_FACET); 217 } 218 219 fireEvent(newRootContainer, session, NuxeoDriveEvents.ABOUT_TO_REGISTER_ROOT, userName); 220 221 @SuppressWarnings("unchecked") 222 List<Map<String, Object>> subscriptions = (List<Map<String, Object>>) newRootContainer.getPropertyValue( 223 DRIVE_SUBSCRIPTIONS_PROPERTY); 224 boolean updated = false; 225 for (Map<String, Object> subscription : subscriptions) { 226 if (userName.equals(subscription.get("username"))) { 227 subscription.put("enabled", Boolean.TRUE); 228 subscription.put("lastChangeDate", Calendar.getInstance(UTC)); 229 updated = true; 230 break; 231 } 232 } 233 if (!updated) { 234 Map<String, Object> subscription = new HashMap<String, Object>(); 235 subscription.put("username", userName); 236 subscription.put("enabled", Boolean.TRUE); 237 subscription.put("lastChangeDate", Calendar.getInstance(UTC)); 238 subscriptions.add(subscription); 239 } 240 newRootContainer.setPropertyValue(DRIVE_SUBSCRIPTIONS_PROPERTY, (Serializable) subscriptions); 241 newRootContainer.putContextData(NXAuditEventsService.DISABLE_AUDIT_LOGGER, true); 242 newRootContainer.putContextData(NotificationConstants.DISABLE_NOTIFICATION_SERVICE, true); 243 newRootContainer.putContextData(CoreSession.SOURCE, "drive"); 244 DocumentModel savedNewRootContainer = session.saveDocument(newRootContainer); 245 newRootContainer.putContextData(NXAuditEventsService.DISABLE_AUDIT_LOGGER, false); 246 newRootContainer.putContextData(NotificationConstants.DISABLE_NOTIFICATION_SERVICE, false); 247 fireEvent(savedNewRootContainer, session, NuxeoDriveEvents.ROOT_REGISTERED, userName); 248 session.save(); 249 } 250 }; 251 runner.runUnrestricted(); 252 253 invalidateSynchronizationRootsCache(userName); 254 invalidateCollectionSyncRootMemberCache(userName); 255 } 256 257 @Override 258 public void unregisterSynchronizationRoot(NuxeoPrincipal principal, final DocumentModel rootContainer, 259 CoreSession session) { 260 final String userName = principal.getName(); 261 log.debug("Unregistering synchronization root {} for {}", rootContainer, userName); 262 checkCanUpdateSynchronizationRoot(rootContainer, session); 263 UnrestrictedSessionRunner runner = new UnrestrictedSessionRunner(session) { 264 @Override 265 public void run() { 266 if (!rootContainer.hasFacet(NUXEO_DRIVE_FACET)) { 267 rootContainer.addFacet(NUXEO_DRIVE_FACET); 268 } 269 fireEvent(rootContainer, session, NuxeoDriveEvents.ABOUT_TO_UNREGISTER_ROOT, userName); 270 @SuppressWarnings("unchecked") 271 List<Map<String, Object>> subscriptions = (List<Map<String, Object>>) rootContainer.getPropertyValue( 272 DRIVE_SUBSCRIPTIONS_PROPERTY); 273 for (Map<String, Object> subscription : subscriptions) { 274 if (userName.equals(subscription.get("username"))) { 275 subscription.put("enabled", Boolean.FALSE); 276 subscription.put("lastChangeDate", Calendar.getInstance(UTC)); 277 break; 278 } 279 } 280 rootContainer.setPropertyValue(DRIVE_SUBSCRIPTIONS_PROPERTY, (Serializable) subscriptions); 281 rootContainer.putContextData(NXAuditEventsService.DISABLE_AUDIT_LOGGER, true); 282 rootContainer.putContextData(NotificationConstants.DISABLE_NOTIFICATION_SERVICE, true); 283 rootContainer.putContextData(CoreSession.SOURCE, "drive"); 284 session.saveDocument(rootContainer); 285 rootContainer.putContextData(NXAuditEventsService.DISABLE_AUDIT_LOGGER, false); 286 rootContainer.putContextData(NotificationConstants.DISABLE_NOTIFICATION_SERVICE, false); 287 fireEvent(rootContainer, session, NuxeoDriveEvents.ROOT_UNREGISTERED, userName); 288 session.save(); 289 } 290 }; 291 runner.runUnrestricted(); 292 invalidateSynchronizationRootsCache(userName); 293 invalidateCollectionSyncRootMemberCache(userName); 294 } 295 296 @Override 297 public Set<IdRef> getSynchronizationRootReferences(CoreSession session) { 298 Map<String, SynchronizationRoots> syncRoots = getSynchronizationRoots(session.getPrincipal()); 299 return syncRoots.get(session.getRepositoryName()).getRefs(); 300 } 301 302 @Override 303 public void handleFolderDeletion(IdRef deleted) { 304 clearCache(); 305 } 306 307 protected void fireEvent(DocumentModel sourceDocument, CoreSession session, String eventName, 308 String impactedUserName) { 309 EventService eventService = Framework.getService(EventService.class); 310 DocumentEventContext ctx = new DocumentEventContext(session, session.getPrincipal(), sourceDocument); 311 ctx.setProperty(CoreEventConstants.REPOSITORY_NAME, session.getRepositoryName()); 312 ctx.setProperty(CoreEventConstants.SESSION_ID, session.getSessionId()); 313 ctx.setProperty("category", NuxeoDriveEvents.EVENT_CATEGORY); 314 ctx.setProperty(NuxeoDriveEvents.IMPACTED_USERNAME_PROPERTY, impactedUserName); 315 Event event = ctx.newEvent(eventName); 316 eventService.fireEvent(event); 317 } 318 319 /** 320 * Uses the {@link AuditChangeFinder} to get the summary of document changes for the given user and lower bound. 321 * <p> 322 * The {@link #DOCUMENT_CHANGE_LIMIT_PROPERTY} Framework property is used as a limit of document changes to fetch 323 * from the audit logs. Default value is 1000. If {@code lowerBound} is missing (i.e. set to a negative value), the 324 * filesystem change summary is empty but the returned upper bound is set to the greater event log id so that the 325 * client can reuse it as a starting id for a future incremental diff request. 326 */ 327 @Override 328 public FileSystemChangeSummary getChangeSummary(NuxeoPrincipal principal, Map<String, Set<IdRef>> lastSyncRootRefs, 329 long lowerBound) { 330 Map<String, SynchronizationRoots> roots = getSynchronizationRoots(principal); 331 Map<String, Set<String>> collectionSyncRootMemberIds = getCollectionSyncRootMemberIds(principal); 332 List<FileSystemItemChange> allChanges = new ArrayList<FileSystemItemChange>(); 333 // Compute the list of all repositories to consider for the aggregate summary 334 Set<String> allRepositories = new TreeSet<String>(); 335 allRepositories.addAll(roots.keySet()); 336 allRepositories.addAll(lastSyncRootRefs.keySet()); 337 allRepositories.addAll(collectionSyncRootMemberIds.keySet()); 338 long syncDate; 339 long upperBound = changeFinder.getUpperBound(allRepositories); 340 // Truncate sync date to 0 milliseconds 341 syncDate = System.currentTimeMillis(); 342 syncDate = syncDate - (syncDate % 1000); 343 Boolean hasTooManyChanges = Boolean.FALSE; 344 int limit = Integer.parseInt(Framework.getProperty(DOCUMENT_CHANGE_LIMIT_PROPERTY, "1000")); 345 if (!allRepositories.isEmpty() && lowerBound >= 0 && upperBound > lowerBound) { 346 for (String repositoryName : allRepositories) { 347 try (CloseableCoreSession session = CoreInstance.openCoreSession(repositoryName, principal)) { 348 // Get document changes 349 Set<IdRef> lastRefs = lastSyncRootRefs.get(repositoryName); 350 if (lastRefs == null) { 351 lastRefs = Collections.emptySet(); 352 } 353 SynchronizationRoots activeRoots = roots.get(repositoryName); 354 if (activeRoots == null) { 355 activeRoots = SynchronizationRoots.getEmptyRoots(repositoryName); 356 } 357 Set<String> repoCollectionSyncRootMemberIds = collectionSyncRootMemberIds.get(repositoryName); 358 if (repoCollectionSyncRootMemberIds == null) { 359 repoCollectionSyncRootMemberIds = Collections.emptySet(); 360 } 361 log.debug( 362 "Start: getting FileSystemItem changes for repository {} / user {} between {} and {} with activeRoots = {}", 363 () -> repositoryName, principal::getName, () -> lowerBound, () -> upperBound, 364 activeRoots::getPaths); 365 List<FileSystemItemChange> changes; 366 changes = changeFinder.getFileSystemChanges(session, lastRefs, activeRoots, 367 repoCollectionSyncRootMemberIds, lowerBound, upperBound, limit); 368 allChanges.addAll(changes); 369 } catch (TooManyChangesException e) { 370 hasTooManyChanges = Boolean.TRUE; 371 allChanges.clear(); 372 break; 373 } 374 } 375 } 376 377 // Send back to the client the list of currently active roots to be able 378 // to efficiently detect root unregistration events for the next 379 // incremental change summary 380 Map<String, Set<IdRef>> activeRootRefs = new HashMap<String, Set<IdRef>>(); 381 for (Map.Entry<String, SynchronizationRoots> rootsEntry : roots.entrySet()) { 382 activeRootRefs.put(rootsEntry.getKey(), rootsEntry.getValue().getRefs()); 383 } 384 FileSystemChangeSummary summary = new FileSystemChangeSummaryImpl(allChanges, activeRootRefs, syncDate, 385 upperBound, hasTooManyChanges); 386 log.debug("End: getting {} FileSystemItem changes for user {} between {} and {} with activeRoots = {} -> {}", 387 allChanges::size, principal::getName, () -> lowerBound, () -> upperBound, () -> roots, () -> summary); 388 return summary; 389 390 } 391 392 @Override 393 @SuppressWarnings("unchecked") 394 public Map<String, SynchronizationRoots> getSynchronizationRoots(NuxeoPrincipal principal) { 395 String userName = principal.getName(); 396 Map<String, SynchronizationRoots> syncRoots = (Map<String, SynchronizationRoots>) getSyncRootCache().get( 397 userName); 398 if (syncRoots == null) { 399 syncRoots = computeSynchronizationRoots(computeSyncRootsQuery(userName), principal); 400 getSyncRootCache().put(userName, (Serializable) syncRoots); 401 } 402 return syncRoots; 403 } 404 405 @Override 406 @SuppressWarnings("unchecked") 407 public Map<String, Set<String>> getCollectionSyncRootMemberIds(NuxeoPrincipal principal) { 408 String userName = principal.getName(); 409 Map<String, Set<String>> collSyncRootMemberIds = (Map<String, Set<String>>) getCollectionSyncRootMemberCache().get( 410 userName); 411 if (collSyncRootMemberIds == null) { 412 collSyncRootMemberIds = computeCollectionSyncRootMemberIds(principal); 413 getCollectionSyncRootMemberCache().put(userName, (Serializable) collSyncRootMemberIds); 414 } 415 return collSyncRootMemberIds; 416 } 417 418 @Override 419 public boolean isSynchronizationRoot(NuxeoPrincipal principal, DocumentModel doc) { 420 String repoName = doc.getRepositoryName(); 421 SynchronizationRoots syncRoots = getSynchronizationRoots(principal).get(repoName); 422 return syncRoots.getRefs().contains(doc.getRef()); 423 } 424 425 protected Map<String, SynchronizationRoots> computeSynchronizationRoots(String query, NuxeoPrincipal principal) { 426 Map<String, SynchronizationRoots> syncRoots = new HashMap<String, SynchronizationRoots>(); 427 RepositoryManager repositoryManager = Framework.getService(RepositoryManager.class); 428 for (String repositoryName : repositoryManager.getRepositoryNames()) { 429 try (CloseableCoreSession session = CoreInstance.openCoreSession(repositoryName, principal)) { 430 syncRoots.putAll(queryAndFetchSynchronizationRoots(session, query)); 431 } 432 } 433 return syncRoots; 434 } 435 436 protected Map<String, SynchronizationRoots> queryAndFetchSynchronizationRoots(CoreSession session, String query) { 437 Map<String, SynchronizationRoots> syncRoots = new HashMap<String, SynchronizationRoots>(); 438 Set<IdRef> references = new LinkedHashSet<IdRef>(); 439 Set<String> paths = new LinkedHashSet<String>(); 440 try (IterableQueryResult results = session.queryAndFetch(query, NXQL.NXQL)) { 441 for (Map<String, Serializable> result : results) { 442 IdRef docRef = new IdRef(result.get("ecm:uuid").toString()); 443 try { 444 DocumentModel doc = session.getDocument(docRef); 445 references.add(docRef); 446 paths.add(doc.getPathAsString()); 447 } catch (DocumentNotFoundException e) { 448 log.warn("Document {} not found, not adding it to the list of synchronization roots for user {}.", 449 () -> docRef, session::getPrincipal); 450 } catch (DocumentSecurityException e) { 451 log.warn("User {} cannot access document {}, not adding it to the list of synchronization roots.", 452 session::getPrincipal, () -> docRef); 453 } 454 } 455 } 456 SynchronizationRoots repoSyncRoots = new SynchronizationRoots(session.getRepositoryName(), paths, references); 457 syncRoots.put(session.getRepositoryName(), repoSyncRoots); 458 return syncRoots; 459 } 460 461 @SuppressWarnings("unchecked") 462 protected Map<String, Set<String>> computeCollectionSyncRootMemberIds(NuxeoPrincipal principal) { 463 Map<String, Set<String>> collectionSyncRootMemberIds = new HashMap<String, Set<String>>(); 464 PageProviderService pageProviderService = Framework.getService(PageProviderService.class); 465 RepositoryManager repositoryManager = Framework.getService(RepositoryManager.class); 466 for (String repositoryName : repositoryManager.getRepositoryNames()) { 467 Set<String> collectionMemberIds = new HashSet<String>(); 468 try (CloseableCoreSession session = CoreInstance.openCoreSession(repositoryName, principal)) { 469 Map<String, Serializable> props = new HashMap<String, Serializable>(); 470 props.put(CORE_SESSION_PROPERTY, (Serializable) session); 471 PageProvider<DocumentModel> collectionPageProvider = (PageProvider<DocumentModel>) pageProviderService.getPageProvider( 472 CollectionConstants.ALL_COLLECTIONS_PAGE_PROVIDER, null, null, 0L, props); 473 List<DocumentModel> collections = collectionPageProvider.getCurrentPage(); 474 for (DocumentModel collection : collections) { 475 if (isSynchronizationRoot(principal, collection)) { 476 PageProvider<DocumentModel> collectionMemberPageProvider = (PageProvider<DocumentModel>) pageProviderService.getPageProvider( 477 CollectionConstants.COLLECTION_CONTENT_PAGE_PROVIDER, null, 478 COLLECTION_CONTENT_PAGE_SIZE, 0L, props, collection.getId()); 479 List<DocumentModel> collectionMembers = collectionMemberPageProvider.getCurrentPage(); 480 for (DocumentModel collectionMember : collectionMembers) { 481 collectionMemberIds.add(collectionMember.getId()); 482 } 483 } 484 } 485 collectionSyncRootMemberIds.put(repositoryName, collectionMemberIds); 486 } 487 } 488 return collectionSyncRootMemberIds; 489 } 490 491 protected void checkCanUpdateSynchronizationRoot(DocumentModel newRootContainer, CoreSession session) { 492 // Cannot update a proxy or a version 493 if (newRootContainer.isProxy() || newRootContainer.isVersion()) { 494 throw new NuxeoException(String.format( 495 "Document '%s' (%s) is not a suitable synchronization root" 496 + " as it is either a readonly proxy or an archived version.", 497 newRootContainer.getTitle(), newRootContainer.getRef())); 498 } 499 } 500 501 @Override 502 public FileSystemChangeFinder getChangeFinder() { 503 return changeFinder; 504 } 505 506 /** 507 * @since 5.9.5 508 */ 509 protected String computeSyncRootsQuery(String username) { 510 return String.format( 511 "SELECT ecm:uuid FROM Document" // 512 + " WHERE %s/*1/username = %s" // 513 + " AND %s/*1/enabled = 1" // 514 + " AND ecm:isTrashed = 0" // 515 + " AND ecm:isVersion = 0" // 516 + " ORDER BY dc:title, dc:created DESC", 517 DRIVE_SUBSCRIPTIONS_PROPERTY, NXQLQueryBuilder.prepareStringLiteral(username, true, true), 518 DRIVE_SUBSCRIPTIONS_PROPERTY); 519 } 520 521 @Override 522 public void addToLocallyEditedCollection(CoreSession session, DocumentModel doc) { 523 524 // Add document to "Locally Edited" collection, creating if if not 525 // exists 526 CollectionManager cm = Framework.getService(CollectionManager.class); 527 DocumentModel userCollections = cm.getUserDefaultCollections(session); 528 DocumentRef locallyEditedCollectionRef = new PathRef(userCollections.getPath().toString(), 529 LOCALLY_EDITED_COLLECTION_NAME); 530 DocumentModel locallyEditedCollection = null; 531 if (session.exists(locallyEditedCollectionRef)) { 532 locallyEditedCollection = session.getDocument(locallyEditedCollectionRef); 533 cm.addToCollection(locallyEditedCollection, doc, session); 534 } else { 535 cm.addToNewCollection(LOCALLY_EDITED_COLLECTION_NAME, "Documents locally edited with Nuxeo Drive", doc, 536 session); 537 locallyEditedCollection = session.getDocument(locallyEditedCollectionRef); 538 } 539 540 // Register "Locally Edited" collection as a synchronization root if not 541 // already the case 542 Set<IdRef> syncRootRefs = getSynchronizationRootReferences(session); 543 if (!syncRootRefs.contains(new IdRef(locallyEditedCollection.getId()))) { 544 registerSynchronizationRoot(session.getPrincipal(), locallyEditedCollection, session); 545 } 546 } 547 548 /*------------------------ DefaultComponent -----------------------------*/ 549 @Override 550 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 551 if (CHANGE_FINDER_EP.equals(extensionPoint)) { 552 changeFinderRegistry.addContribution((ChangeFinderDescriptor) contribution); 553 } else { 554 log.error("Unknown extension point {}", extensionPoint); 555 } 556 } 557 558 @Override 559 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 560 if (CHANGE_FINDER_EP.equals(extensionPoint)) { 561 changeFinderRegistry.removeContribution((ChangeFinderDescriptor) contribution); 562 } else { 563 log.error("Unknown extension point {}", extensionPoint); 564 } 565 } 566 567 @Override 568 public void activate(ComponentContext context) { 569 super.activate(context); 570 if (changeFinderRegistry == null) { 571 changeFinderRegistry = new ChangeFinderRegistry(); 572 } 573 } 574 575 @Override 576 public void deactivate(ComponentContext context) { 577 super.deactivate(context); 578 changeFinderRegistry = null; 579 } 580 581 @Override 582 public int getApplicationStartedOrder() { 583 ComponentInstance cacheComponent = Framework.getRuntime() 584 .getComponentInstance("org.nuxeo.ecm.core.cache.CacheService"); 585 if (cacheComponent == null || cacheComponent.getInstance() == null) { 586 return super.getApplicationStartedOrder(); 587 } 588 return ((DefaultComponent) cacheComponent.getInstance()).getApplicationStartedOrder() + 1; 589 } 590 591 /** 592 * Sorts the contributed factories according to their order. 593 */ 594 @Override 595 public void start(ComponentContext context) { 596 syncRootCache = Framework.getService(CacheService.class).getCache(DRIVE_SYNC_ROOT_CACHE); 597 collectionSyncRootMemberCache = Framework.getService(CacheService.class) 598 .getCache(DRIVE_COLLECTION_SYNC_ROOT__MEMBER_CACHE); 599 changeFinder = changeFinderRegistry.changeFinder; 600 } 601 602 @Override 603 public void stop(ComponentContext context) { 604 syncRootCache = null; 605 collectionSyncRootMemberCache = null; 606 changeFinder = null; 607 } 608 609}