001/* 002 * (C) Copyright 2012-2018 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Antoine Taillefer <ataillefer@nuxeo.com> 018 */ 019package org.nuxeo.drive.service.impl; 020 021import java.util.ArrayList; 022import java.util.Iterator; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026import java.util.concurrent.Semaphore; 027 028import org.apache.commons.lang3.StringUtils; 029import org.apache.logging.log4j.LogManager; 030import org.apache.logging.log4j.Logger; 031import org.nuxeo.drive.adapter.FileSystemItem; 032import org.nuxeo.drive.adapter.FolderItem; 033import org.nuxeo.drive.adapter.NuxeoDriveContribException; 034import org.nuxeo.drive.adapter.RootlessItemException; 035import org.nuxeo.drive.service.FileSystemItemAdapterService; 036import org.nuxeo.drive.service.FileSystemItemFactory; 037import org.nuxeo.drive.service.TopLevelFolderItemFactory; 038import org.nuxeo.drive.service.VirtualFolderItemFactory; 039import org.nuxeo.ecm.core.api.DocumentModel; 040import org.nuxeo.runtime.api.Framework; 041import org.nuxeo.runtime.model.ComponentContext; 042import org.nuxeo.runtime.model.ComponentInstance; 043import org.nuxeo.runtime.model.DefaultComponent; 044import org.nuxeo.runtime.services.config.ConfigurationService; 045 046/** 047 * Default implementation of the {@link FileSystemItemAdapterService}. 048 * 049 * @author Antoine Taillefer 050 */ 051public class FileSystemItemAdapterServiceImpl extends DefaultComponent implements FileSystemItemAdapterService { 052 053 private static final Logger log = LogManager.getLogger(FileSystemItemAdapterServiceImpl.class); 054 055 public static final String FILE_SYSTEM_ITEM_FACTORY_EP = "fileSystemItemFactory"; 056 057 public static final String TOP_LEVEL_FOLDER_ITEM_FACTORY_EP = "topLevelFolderItemFactory"; 058 059 public static final String ACTIVE_FILE_SYSTEM_ITEM_FACTORIES_EP = "activeFileSystemItemFactories"; 060 061 protected static final String CONCURRENT_SCROLL_BATCH_LIMIT = "org.nuxeo.drive.concurrentScrollBatchLimit"; 062 063 protected static final int CONCURRENT_SCROLL_BATCH_LIMIT_DEFAULT = 4; 064 065 protected TopLevelFolderItemFactoryRegistry topLevelFolderItemFactoryRegistry; 066 067 protected FileSystemItemFactoryRegistry fileSystemItemFactoryRegistry; 068 069 protected ActiveTopLevelFolderItemFactoryRegistry activeTopLevelFolderItemFactoryRegistry; 070 071 protected ActiveFileSystemItemFactoryRegistry activeFileSystemItemFactoryRegistry; 072 073 protected TopLevelFolderItemFactory topLevelFolderItemFactory; 074 075 protected List<FileSystemItemFactoryWrapper> fileSystemItemFactories; 076 077 protected Semaphore scrollBatchSemaphore; 078 079 /*------------------------ DefaultComponent -----------------------------*/ 080 @Override 081 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 082 if (FILE_SYSTEM_ITEM_FACTORY_EP.equals(extensionPoint)) { 083 fileSystemItemFactoryRegistry.addContribution((FileSystemItemFactoryDescriptor) contribution); 084 } else if (TOP_LEVEL_FOLDER_ITEM_FACTORY_EP.equals(extensionPoint)) { 085 topLevelFolderItemFactoryRegistry.addContribution((TopLevelFolderItemFactoryDescriptor) contribution); 086 } else if (ACTIVE_FILE_SYSTEM_ITEM_FACTORIES_EP.equals(extensionPoint)) { 087 if (contribution instanceof ActiveTopLevelFolderItemFactoryDescriptor) { 088 activeTopLevelFolderItemFactoryRegistry.addContribution( 089 (ActiveTopLevelFolderItemFactoryDescriptor) contribution); 090 } else if (contribution instanceof ActiveFileSystemItemFactoriesDescriptor) { 091 activeFileSystemItemFactoryRegistry.addContribution( 092 (ActiveFileSystemItemFactoriesDescriptor) contribution); 093 } 094 } else { 095 log.error("Unknown extension point {}", extensionPoint); 096 } 097 } 098 099 @Override 100 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 101 if (FILE_SYSTEM_ITEM_FACTORY_EP.equals(extensionPoint)) { 102 fileSystemItemFactoryRegistry.removeContribution((FileSystemItemFactoryDescriptor) contribution); 103 } else if (TOP_LEVEL_FOLDER_ITEM_FACTORY_EP.equals(extensionPoint)) { 104 topLevelFolderItemFactoryRegistry.removeContribution((TopLevelFolderItemFactoryDescriptor) contribution); 105 } else if (ACTIVE_FILE_SYSTEM_ITEM_FACTORIES_EP.equals(extensionPoint)) { 106 if (contribution instanceof ActiveTopLevelFolderItemFactoryDescriptor) { 107 activeTopLevelFolderItemFactoryRegistry.removeContribution( 108 (ActiveTopLevelFolderItemFactoryDescriptor) contribution); 109 } else if (contribution instanceof ActiveFileSystemItemFactoriesDescriptor) { 110 activeFileSystemItemFactoryRegistry.removeContribution( 111 (ActiveFileSystemItemFactoriesDescriptor) contribution); 112 } 113 } else { 114 log.error("Unknown extension point {}", extensionPoint); 115 } 116 } 117 118 @Override 119 public void activate(ComponentContext context) { 120 fileSystemItemFactoryRegistry = new FileSystemItemFactoryRegistry(); 121 topLevelFolderItemFactoryRegistry = new TopLevelFolderItemFactoryRegistry(); 122 activeTopLevelFolderItemFactoryRegistry = new ActiveTopLevelFolderItemFactoryRegistry(); 123 activeFileSystemItemFactoryRegistry = new ActiveFileSystemItemFactoryRegistry(); 124 fileSystemItemFactories = new ArrayList<>(); 125 } 126 127 @Override 128 public void deactivate(ComponentContext context) { 129 super.deactivate(context); 130 fileSystemItemFactoryRegistry = null; 131 topLevelFolderItemFactoryRegistry = null; 132 activeTopLevelFolderItemFactoryRegistry = null; 133 activeFileSystemItemFactoryRegistry = null; 134 fileSystemItemFactories = null; 135 } 136 137 /** 138 * Sorts the contributed factories according to their order and initializes the {@link #scrollBatchSemaphore}. 139 */ 140 @Override 141 public void start(ComponentContext context) { 142 topLevelFolderItemFactory = topLevelFolderItemFactoryRegistry.getActiveFactory( 143 activeTopLevelFolderItemFactoryRegistry.activeFactory); 144 fileSystemItemFactories = fileSystemItemFactoryRegistry.getOrderedActiveFactories( 145 activeFileSystemItemFactoryRegistry.activeFactories); 146 int concurrentScrollBatchLimit = Framework.getService(ConfigurationService.class) 147 .getInteger(CONCURRENT_SCROLL_BATCH_LIMIT, 148 CONCURRENT_SCROLL_BATCH_LIMIT_DEFAULT); 149 scrollBatchSemaphore = new Semaphore(concurrentScrollBatchLimit, false); 150 } 151 152 @Override 153 public void stop(ComponentContext context) throws InterruptedException { 154 topLevelFolderItemFactory = null; 155 fileSystemItemFactories = null; 156 scrollBatchSemaphore = null; 157 } 158 159 /*------------------------ FileSystemItemAdapterService -----------------------*/ 160 @Override 161 public FileSystemItem getFileSystemItem(DocumentModel doc) { 162 return getFileSystemItem(doc, false, null, false, false, true); 163 } 164 165 @Override 166 public FileSystemItem getFileSystemItem(DocumentModel doc, boolean includeDeleted) { 167 return getFileSystemItem(doc, false, null, includeDeleted, false, true); 168 } 169 170 @Override 171 public FileSystemItem getFileSystemItem(DocumentModel doc, boolean includeDeleted, 172 boolean relaxSyncRootConstraint) { 173 return getFileSystemItem(doc, false, null, includeDeleted, relaxSyncRootConstraint, true); 174 } 175 176 @Override 177 public FileSystemItem getFileSystemItem(DocumentModel doc, boolean includeDeleted, boolean relaxSyncRootConstraint, 178 boolean getLockInfo) { 179 return getFileSystemItem(doc, false, null, includeDeleted, relaxSyncRootConstraint, getLockInfo); 180 } 181 182 @Override 183 public FileSystemItem getFileSystemItem(DocumentModel doc, FolderItem parentItem) { 184 return getFileSystemItem(doc, true, parentItem, false, false, true); 185 } 186 187 @Override 188 public FileSystemItem getFileSystemItem(DocumentModel doc, FolderItem parentItem, boolean includeDeleted) { 189 return getFileSystemItem(doc, true, parentItem, includeDeleted, false, true); 190 } 191 192 @Override 193 public FileSystemItem getFileSystemItem(DocumentModel doc, FolderItem parentItem, boolean includeDeleted, 194 boolean relaxSyncRootConstraint) { 195 return getFileSystemItem(doc, true, parentItem, includeDeleted, relaxSyncRootConstraint, true); 196 } 197 198 @Override 199 public FileSystemItem getFileSystemItem(DocumentModel doc, FolderItem parentItem, boolean includeDeleted, 200 boolean relaxSyncRootConstraint, boolean getLockInfo) { 201 return getFileSystemItem(doc, true, parentItem, includeDeleted, relaxSyncRootConstraint, getLockInfo); 202 } 203 204 /** 205 * Iterates on the ordered contributed file system item factories until if finds one that can handle the given 206 * {@link FileSystemItem} id. 207 */ 208 @Override 209 public FileSystemItemFactory getFileSystemItemFactoryForId(String id) { 210 Iterator<FileSystemItemFactoryWrapper> factoriesIt = fileSystemItemFactories.iterator(); 211 while (factoriesIt.hasNext()) { 212 FileSystemItemFactoryWrapper factoryWrapper = factoriesIt.next(); 213 FileSystemItemFactory factory = factoryWrapper.getFactory(); 214 if (factory.canHandleFileSystemItemId(id)) { 215 return factory; 216 } 217 } 218 // No fileSystemItemFactory found, try the topLevelFolderItemFactory 219 if (getTopLevelFolderItemFactory().canHandleFileSystemItemId(id)) { 220 return getTopLevelFolderItemFactory(); 221 } 222 throw new NuxeoDriveContribException(String.format( 223 "No fileSystemItemFactory found for FileSystemItem with id %s. Please check the contributions to the following extension point: <extension target=\"org.nuxeo.drive.service.FileSystemItemAdapterService\" point=\"fileSystemItemFactory\"> and make sure there is at least one defining a FileSystemItemFactory class for which the #canHandleFileSystemItemId(String id) method returns true.", 224 id)); 225 } 226 227 @Override 228 public TopLevelFolderItemFactory getTopLevelFolderItemFactory() { 229 if (topLevelFolderItemFactory == null) { 230 throw new NuxeoDriveContribException( 231 "Found no active top level folder item factory. Please check there is a contribution to the following extension point: <extension target=\"org.nuxeo.drive.service.FileSystemItemAdapterService\" point=\"topLevelFolderItemFactory\"> and to <extension target=\"org.nuxeo.drive.service.FileSystemItemAdapterService\" point=\"activeTopLevelFolderItemFactory\">."); 232 } 233 return topLevelFolderItemFactory; 234 } 235 236 @Override 237 public VirtualFolderItemFactory getVirtualFolderItemFactory(String factoryName) { 238 FileSystemItemFactory factory = getFileSystemItemFactory(factoryName); 239 if (factory == null) { 240 throw new NuxeoDriveContribException(String.format( 241 "No factory named %s. Please check the contributions to the following extension point: <extension target=\"org.nuxeo.drive.service.FileSystemItemAdapterService\" point=\"fileSystemItemFactory\">.", 242 factoryName)); 243 } 244 if (!(factory instanceof VirtualFolderItemFactory)) { 245 throw new NuxeoDriveContribException( 246 String.format("Factory class %s for factory %s is not a VirtualFolderItemFactory.", 247 factory.getClass().getName(), factory.getName())); 248 } 249 return (VirtualFolderItemFactory) factory; 250 } 251 252 @Override 253 public Set<String> getActiveFileSystemItemFactories() { 254 if (activeFileSystemItemFactoryRegistry.activeFactories.isEmpty()) { 255 throw new NuxeoDriveContribException( 256 "Found no active file system item factories. Please check there is a contribution to the following extension point: <extension target=\"org.nuxeo.drive.service.FileSystemItemAdapterService\" point=\"activeFileSystemItemFactories\"> declaring at least one factory."); 257 } 258 return activeFileSystemItemFactoryRegistry.activeFactories; 259 } 260 261 @Override 262 public Semaphore getScrollBatchSemaphore() { 263 return scrollBatchSemaphore; 264 } 265 266 /*------------------------- For test purpose ----------------------------------*/ 267 public Map<String, FileSystemItemFactoryDescriptor> getFileSystemItemFactoryDescriptors() { 268 return fileSystemItemFactoryRegistry.factoryDescriptors; 269 } 270 271 public List<FileSystemItemFactoryWrapper> getFileSystemItemFactories() { 272 return fileSystemItemFactories; 273 } 274 275 public FileSystemItemFactory getFileSystemItemFactory(String name) { 276 for (FileSystemItemFactoryWrapper factoryWrapper : fileSystemItemFactories) { 277 FileSystemItemFactory factory = factoryWrapper.getFactory(); 278 if (name.equals(factory.getName())) { 279 return factory; 280 } 281 } 282 log.debug("No fileSystemItemFactory named {}, returning null.", name); 283 return null; 284 } 285 286 /** 287 * @deprecated since 9.3 this is method is not needed anymore with hot reload and standby strategy, but kept due to 288 * some issues in operation NuxeoDriveSetActiveFactories which freeze Jetty in unit tests when wanting 289 * to use standby strategy 290 */ 291 @Deprecated 292 public void setActiveFactories() { 293 topLevelFolderItemFactory = topLevelFolderItemFactoryRegistry.getActiveFactory( 294 activeTopLevelFolderItemFactoryRegistry.activeFactory); 295 fileSystemItemFactories = fileSystemItemFactoryRegistry.getOrderedActiveFactories( 296 activeFileSystemItemFactoryRegistry.activeFactories); 297 } 298 299 /*--------------------------- Protected ---------------------------------------*/ 300 /** 301 * Tries to adapt the given document as the top level {@link FolderItem}. If it doesn't match, iterates on the 302 * ordered contributed file system item factories until it finds one that matches and retrieves a non null 303 * {@link FileSystemItem} for the given document. A file system item factory matches if: 304 * <ul> 305 * <li>It is not bound to any docType nor facet (this is the case for the default factory contribution 306 * {@code defaultFileSystemItemFactory} bound to {@link DefaultFileSystemItemFactory})</li> 307 * <li>It is bound to a docType that matches the given doc's type</li> 308 * <li>It is bound to a facet that matches one of the given doc's facets</li> 309 * </ul> 310 */ 311 protected FileSystemItem getFileSystemItem(DocumentModel doc, boolean forceParentItem, FolderItem parentItem, 312 boolean includeDeleted, boolean relaxSyncRootConstraint, boolean getLockInfo) { 313 314 FileSystemItem fileSystemItem; 315 316 // Try the topLevelFolderItemFactory 317 if (forceParentItem) { 318 fileSystemItem = getTopLevelFolderItemFactory().getFileSystemItem(doc, parentItem, includeDeleted, 319 relaxSyncRootConstraint, getLockInfo); 320 } else { 321 fileSystemItem = getTopLevelFolderItemFactory().getFileSystemItem(doc, includeDeleted, 322 relaxSyncRootConstraint, getLockInfo); 323 } 324 if (fileSystemItem != null) { 325 return fileSystemItem; 326 } else { 327 log.debug( 328 "The topLevelFolderItemFactory is not able to adapt document {} as a FileSystemItem => trying fileSystemItemFactories.", 329 doc::getId); 330 } 331 332 // Try the fileSystemItemFactories 333 FileSystemItemFactoryWrapper matchingFactory = null; 334 335 Iterator<FileSystemItemFactoryWrapper> factoriesIt = fileSystemItemFactories.iterator(); 336 while (factoriesIt.hasNext()) { 337 FileSystemItemFactoryWrapper factory = factoriesIt.next(); 338 log.debug("Trying to adapt document {} (path: {}) as a FileSystemItem with factory {}", doc::getId, 339 doc::getPathAsString, () -> factory.getFactory().getName()); 340 if (generalFactoryMatches(factory) || docTypeFactoryMatches(factory, doc) 341 || facetFactoryMatches(factory, doc, relaxSyncRootConstraint)) { 342 matchingFactory = factory; 343 try { 344 if (forceParentItem) { 345 fileSystemItem = factory.getFactory().getFileSystemItem(doc, parentItem, includeDeleted, 346 relaxSyncRootConstraint, getLockInfo); 347 } else { 348 fileSystemItem = factory.getFactory().getFileSystemItem(doc, includeDeleted, 349 relaxSyncRootConstraint, getLockInfo); 350 } 351 } catch (RootlessItemException e) { 352 // Give more information in the exception message on the 353 // document whose adaption failed to recursively find the 354 // top level item. 355 throw new RootlessItemException(String.format( 356 "Cannot find path to registered top" + " level when adapting document " 357 + " '%s' (path: %s) with factory %s", 358 doc.getTitle(), doc.getPathAsString(), factory.getFactory().getName()), e); 359 } 360 if (fileSystemItem != null) { 361 log.debug("Adapted document '{}' (path: {}) to item with path {} with factory {}", doc::getTitle, 362 doc::getPathAsString, fileSystemItem::getPath, () -> factory.getFactory().getName()); 363 return fileSystemItem; 364 } 365 } 366 } 367 368 if (matchingFactory == null) { 369 log.debug( 370 "None of the fileSystemItemFactories matches document {} => returning null. Please check the contributions to the following extension point: <extension target=\"org.nuxeo.drive.service.FileSystemItemAdapterService\" point=\"fileSystemItemFactory\">.", 371 doc::getId); 372 } else { 373 log.debug( 374 "None of the fileSystemItemFactories matching document {} were able to adapt this document as a FileSystemItem => returning null.", 375 doc::getId); 376 } 377 return fileSystemItem; 378 } 379 380 protected boolean generalFactoryMatches(FileSystemItemFactoryWrapper factory) { 381 boolean matches = StringUtils.isEmpty(factory.getDocType()) && StringUtils.isEmpty(factory.getFacet()); 382 if (matches) { 383 log.trace("General factory {} matches", factory); 384 } 385 return matches; 386 } 387 388 protected boolean docTypeFactoryMatches(FileSystemItemFactoryWrapper factory, DocumentModel doc) { 389 boolean matches = !StringUtils.isEmpty(factory.getDocType()) && factory.getDocType().equals(doc.getType()); 390 if (matches) { 391 log.trace("DocType factory {} matches for doc {} (path: {})", () -> factory, doc::getId, 392 doc::getPathAsString); 393 } 394 return matches; 395 } 396 397 protected boolean facetFactoryMatches(FileSystemItemFactoryWrapper factory, DocumentModel doc, 398 boolean relaxSyncRootConstraint) { 399 if (!StringUtils.isEmpty(factory.getFacet())) { 400 for (String docFacet : doc.getFacets()) { 401 if (factory.getFacet().equals(docFacet)) { 402 // Handle synchronization root case 403 if (NuxeoDriveManagerImpl.NUXEO_DRIVE_FACET.equals(docFacet)) { 404 boolean matches = syncRootFactoryMatches(doc, relaxSyncRootConstraint); 405 if (matches) { 406 log.trace("Facet factory {} matches for doc {} (path: {})", () -> factory, doc::getId, 407 doc::getPathAsString); 408 } 409 return matches; 410 } else { 411 log.trace("Facet factory {} matches for doc {} (path: {})", () -> factory, doc::getId, 412 doc::getPathAsString); 413 return true; 414 } 415 } 416 } 417 } 418 return false; 419 } 420 421 @SuppressWarnings("unchecked") 422 protected boolean syncRootFactoryMatches(DocumentModel doc, boolean relaxSyncRootConstraint) { 423 String userName = doc.getPrincipal().getName(); 424 List<Map<String, Object>> subscriptions = (List<Map<String, Object>>) doc.getPropertyValue( 425 NuxeoDriveManagerImpl.DRIVE_SUBSCRIPTIONS_PROPERTY); 426 for (Map<String, Object> subscription : subscriptions) { 427 if (Boolean.TRUE.equals(subscription.get("enabled"))) { 428 if (userName.equals(subscription.get("username"))) { 429 log.trace("Doc {} (path: {}) registered as a sync root for user {}", doc::getId, 430 doc::getPathAsString, () -> userName); 431 return true; 432 } 433 if (relaxSyncRootConstraint) { 434 log.trace( 435 "Doc {} (path: {}) registered as a sync root for at least one user (relaxSyncRootConstraint is true)", 436 doc::getId, doc::getPathAsString); 437 return true; 438 } 439 } 440 } 441 return false; 442 } 443 444}