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