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