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 <[email protected]> 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 String 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 = Integer.parseInt(Framework.getService(ConfigurationService.class).getProperty( 147 CONCURRENT_SCROLL_BATCH_LIMIT, CONCURRENT_SCROLL_BATCH_LIMIT_DEFAULT)); 148 scrollBatchSemaphore = new Semaphore(concurrentScrollBatchLimit, false); 149 } 150 151 @Override 152 public void stop(ComponentContext context) throws InterruptedException { 153 topLevelFolderItemFactory = null; 154 fileSystemItemFactories = null; 155 scrollBatchSemaphore = null; 156 } 157 158 /*------------------------ FileSystemItemAdapterService -----------------------*/ 159 @Override 160 public FileSystemItem getFileSystemItem(DocumentModel doc) { 161 return getFileSystemItem(doc, false, null, false, false, true); 162 } 163 164 @Override 165 public FileSystemItem getFileSystemItem(DocumentModel doc, boolean includeDeleted) { 166 return getFileSystemItem(doc, false, null, includeDeleted, false, true); 167 } 168 169 @Override 170 public FileSystemItem getFileSystemItem(DocumentModel doc, boolean includeDeleted, 171 boolean relaxSyncRootConstraint) { 172 return getFileSystemItem(doc, false, null, includeDeleted, relaxSyncRootConstraint, true); 173 } 174 175 @Override 176 public FileSystemItem getFileSystemItem(DocumentModel doc, boolean includeDeleted, boolean relaxSyncRootConstraint, 177 boolean getLockInfo) { 178 return getFileSystemItem(doc, false, null, includeDeleted, relaxSyncRootConstraint, getLockInfo); 179 } 180 181 @Override 182 public FileSystemItem getFileSystemItem(DocumentModel doc, FolderItem parentItem) { 183 return getFileSystemItem(doc, true, parentItem, false, false, true); 184 } 185 186 @Override 187 public FileSystemItem getFileSystemItem(DocumentModel doc, FolderItem parentItem, boolean includeDeleted) { 188 return getFileSystemItem(doc, true, parentItem, includeDeleted, false, true); 189 } 190 191 @Override 192 public FileSystemItem getFileSystemItem(DocumentModel doc, FolderItem parentItem, boolean includeDeleted, 193 boolean relaxSyncRootConstraint) { 194 return getFileSystemItem(doc, true, parentItem, includeDeleted, relaxSyncRootConstraint, true); 195 } 196 197 @Override 198 public FileSystemItem getFileSystemItem(DocumentModel doc, FolderItem parentItem, boolean includeDeleted, 199 boolean relaxSyncRootConstraint, boolean getLockInfo) { 200 return getFileSystemItem(doc, true, parentItem, includeDeleted, relaxSyncRootConstraint, getLockInfo); 201 } 202 203 /** 204 * Iterates on the ordered contributed file system item factories until if finds one that can handle the given 205 * {@link FileSystemItem} id. 206 */ 207 @Override 208 public FileSystemItemFactory getFileSystemItemFactoryForId(String id) { 209 Iterator<FileSystemItemFactoryWrapper> factoriesIt = fileSystemItemFactories.iterator(); 210 while (factoriesIt.hasNext()) { 211 FileSystemItemFactoryWrapper factoryWrapper = factoriesIt.next(); 212 FileSystemItemFactory factory = factoryWrapper.getFactory(); 213 if (factory.canHandleFileSystemItemId(id)) { 214 return factory; 215 } 216 } 217 // No fileSystemItemFactory found, try the topLevelFolderItemFactory 218 if (getTopLevelFolderItemFactory().canHandleFileSystemItemId(id)) { 219 return getTopLevelFolderItemFactory(); 220 } 221 throw new NuxeoDriveContribException(String.format( 222 "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.", 223 id)); 224 } 225 226 @Override 227 public TopLevelFolderItemFactory getTopLevelFolderItemFactory() { 228 if (topLevelFolderItemFactory == null) { 229 throw new NuxeoDriveContribException( 230 "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\">."); 231 } 232 return topLevelFolderItemFactory; 233 } 234 235 @Override 236 public VirtualFolderItemFactory getVirtualFolderItemFactory(String factoryName) { 237 FileSystemItemFactory factory = getFileSystemItemFactory(factoryName); 238 if (factory == null) { 239 throw new NuxeoDriveContribException(String.format( 240 "No factory named %s. Please check the contributions to the following extension point: <extension target=\"org.nuxeo.drive.service.FileSystemItemAdapterService\" point=\"fileSystemItemFactory\">.", 241 factoryName)); 242 } 243 if (!(factory instanceof VirtualFolderItemFactory)) { 244 throw new NuxeoDriveContribException( 245 String.format("Factory class %s for factory %s is not a VirtualFolderItemFactory.", 246 factory.getClass().getName(), factory.getName())); 247 } 248 return (VirtualFolderItemFactory) factory; 249 } 250 251 @Override 252 public Set<String> getActiveFileSystemItemFactories() { 253 if (activeFileSystemItemFactoryRegistry.activeFactories.isEmpty()) { 254 throw new NuxeoDriveContribException( 255 "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."); 256 } 257 return activeFileSystemItemFactoryRegistry.activeFactories; 258 } 259 260 @Override 261 public Semaphore getScrollBatchSemaphore() { 262 return scrollBatchSemaphore; 263 } 264 265 /*------------------------- For test purpose ----------------------------------*/ 266 public Map<String, FileSystemItemFactoryDescriptor> getFileSystemItemFactoryDescriptors() { 267 return fileSystemItemFactoryRegistry.factoryDescriptors; 268 } 269 270 public List<FileSystemItemFactoryWrapper> getFileSystemItemFactories() { 271 return fileSystemItemFactories; 272 } 273 274 public FileSystemItemFactory getFileSystemItemFactory(String name) { 275 for (FileSystemItemFactoryWrapper factoryWrapper : fileSystemItemFactories) { 276 FileSystemItemFactory factory = factoryWrapper.getFactory(); 277 if (name.equals(factory.getName())) { 278 return factory; 279 } 280 } 281 log.debug("No fileSystemItemFactory named {}, returning null.", name); 282 return null; 283 } 284 285 /** 286 * @deprecated since 9.3 this is method is not needed anymore with hot reload and standby strategy, but kept due to 287 * some issues in operation NuxeoDriveSetActiveFactories which freeze Jetty in unit tests when wanting 288 * to use standby strategy 289 */ 290 @Deprecated 291 public void setActiveFactories() { 292 topLevelFolderItemFactory = topLevelFolderItemFactoryRegistry.getActiveFactory( 293 activeTopLevelFolderItemFactoryRegistry.activeFactory); 294 fileSystemItemFactories = fileSystemItemFactoryRegistry.getOrderedActiveFactories( 295 activeFileSystemItemFactoryRegistry.activeFactories); 296 } 297 298 /*--------------------------- Protected ---------------------------------------*/ 299 /** 300 * Tries to adapt the given document as the top level {@link FolderItem}. If it doesn't match, iterates on the 301 * ordered contributed file system item factories until it finds one that matches and retrieves a non null 302 * {@link FileSystemItem} for the given document. A file system item factory matches if: 303 * <ul> 304 * <li>It is not bound to any docType nor facet (this is the case for the default factory contribution 305 * {@code defaultFileSystemItemFactory} bound to {@link DefaultFileSystemItemFactory})</li> 306 * <li>It is bound to a docType that matches the given doc's type</li> 307 * <li>It is bound to a facet that matches one of the given doc's facets</li> 308 * </ul> 309 */ 310 protected FileSystemItem getFileSystemItem(DocumentModel doc, boolean forceParentItem, FolderItem parentItem, 311 boolean includeDeleted, boolean relaxSyncRootConstraint, boolean getLockInfo) { 312 313 FileSystemItem fileSystemItem; 314 315 // Try the topLevelFolderItemFactory 316 if (forceParentItem) { 317 fileSystemItem = getTopLevelFolderItemFactory().getFileSystemItem(doc, parentItem, includeDeleted, 318 relaxSyncRootConstraint, getLockInfo); 319 } else { 320 fileSystemItem = getTopLevelFolderItemFactory().getFileSystemItem(doc, includeDeleted, 321 relaxSyncRootConstraint, getLockInfo); 322 } 323 if (fileSystemItem != null) { 324 return fileSystemItem; 325 } else { 326 log.debug( 327 "The topLevelFolderItemFactory is not able to adapt document {} as a FileSystemItem => trying fileSystemItemFactories.", 328 doc::getId); 329 } 330 331 // Try the fileSystemItemFactories 332 FileSystemItemFactoryWrapper matchingFactory = null; 333 334 Iterator<FileSystemItemFactoryWrapper> factoriesIt = fileSystemItemFactories.iterator(); 335 while (factoriesIt.hasNext()) { 336 FileSystemItemFactoryWrapper factory = factoriesIt.next(); 337 log.debug("Trying to adapt document {} (path: {}) as a FileSystemItem with factory {}", doc::getId, 338 doc::getPathAsString, () -> factory.getFactory().getName()); 339 if (generalFactoryMatches(factory) || docTypeFactoryMatches(factory, doc) 340 || facetFactoryMatches(factory, doc, relaxSyncRootConstraint)) { 341 matchingFactory = factory; 342 try { 343 if (forceParentItem) { 344 fileSystemItem = factory.getFactory().getFileSystemItem(doc, parentItem, includeDeleted, 345 relaxSyncRootConstraint, getLockInfo); 346 } else { 347 fileSystemItem = factory.getFactory().getFileSystemItem(doc, includeDeleted, 348 relaxSyncRootConstraint, getLockInfo); 349 } 350 } catch (RootlessItemException e) { 351 // Give more information in the exception message on the 352 // document whose adaption failed to recursively find the 353 // top level item. 354 throw new RootlessItemException(String.format( 355 "Cannot find path to registered top" + " level when adapting document " 356 + " '%s' (path: %s) with factory %s", 357 doc.getTitle(), doc.getPathAsString(), factory.getFactory().getName()), e); 358 } 359 if (fileSystemItem != null) { 360 log.debug("Adapted document '{}' (path: {}) to item with path {} with factory {}", doc::getTitle, 361 doc::getPathAsString, fileSystemItem::getPath, () -> factory.getFactory().getName()); 362 return fileSystemItem; 363 } 364 } 365 } 366 367 if (matchingFactory == null) { 368 log.debug( 369 "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\">.", 370 doc::getId); 371 } else { 372 log.debug( 373 "None of the fileSystemItemFactories matching document {} were able to adapt this document as a FileSystemItem => returning null.", 374 doc::getId); 375 } 376 return fileSystemItem; 377 } 378 379 protected boolean generalFactoryMatches(FileSystemItemFactoryWrapper factory) { 380 boolean matches = StringUtils.isEmpty(factory.getDocType()) && StringUtils.isEmpty(factory.getFacet()); 381 if (matches) { 382 log.trace("General factory {} matches", factory); 383 } 384 return matches; 385 } 386 387 protected boolean docTypeFactoryMatches(FileSystemItemFactoryWrapper factory, DocumentModel doc) { 388 boolean matches = !StringUtils.isEmpty(factory.getDocType()) && factory.getDocType().equals(doc.getType()); 389 if (matches) { 390 log.trace("DocType factory {} matches for doc {} (path: {})", () -> factory, doc::getId, 391 doc::getPathAsString); 392 } 393 return matches; 394 } 395 396 protected boolean facetFactoryMatches(FileSystemItemFactoryWrapper factory, DocumentModel doc, 397 boolean relaxSyncRootConstraint) { 398 if (!StringUtils.isEmpty(factory.getFacet())) { 399 for (String docFacet : doc.getFacets()) { 400 if (factory.getFacet().equals(docFacet)) { 401 // Handle synchronization root case 402 if (NuxeoDriveManagerImpl.NUXEO_DRIVE_FACET.equals(docFacet)) { 403 boolean matches = syncRootFactoryMatches(doc, relaxSyncRootConstraint); 404 if (matches) { 405 log.trace("Facet factory {} matches for doc {} (path: {})", () -> factory, doc::getId, 406 doc::getPathAsString); 407 } 408 return matches; 409 } else { 410 log.trace("Facet factory {} matches for doc {} (path: {})", () -> factory, doc::getId, 411 doc::getPathAsString); 412 return true; 413 } 414 } 415 } 416 } 417 return false; 418 } 419 420 @SuppressWarnings("unchecked") 421 protected boolean syncRootFactoryMatches(DocumentModel doc, boolean relaxSyncRootConstraint) { 422 String userName = doc.getCoreSession().getPrincipal().getName(); 423 List<Map<String, Object>> subscriptions = (List<Map<String, Object>>) doc.getPropertyValue( 424 NuxeoDriveManagerImpl.DRIVE_SUBSCRIPTIONS_PROPERTY); 425 for (Map<String, Object> subscription : subscriptions) { 426 if (Boolean.TRUE.equals(subscription.get("enabled"))) { 427 if (userName.equals(subscription.get("username"))) { 428 log.trace("Doc {} (path: {}) registered as a sync root for user {}", doc::getId, 429 doc::getPathAsString, () -> userName); 430 return true; 431 } 432 if (relaxSyncRootConstraint) { 433 log.trace( 434 "Doc {} (path: {}) registered as a sync root for at least one user (relaxSyncRootConstraint is true)", 435 doc::getId, doc::getPathAsString); 436 return true; 437 } 438 } 439 } 440 return false; 441 } 442 443}