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 * 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.lang.StringUtils; 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 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 Log log = LogFactory.getLog(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<FileSystemItemFactoryWrapper>(); 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 if (log.isDebugEnabled()) { 282 log.debug(String.format("No fileSystemItemFactory named %s, returning null.", name)); 283 } 284 return null; 285 } 286 287 /** 288 * @deprecated since 9.3 this is method is not needed anymore with hot reload and standby strategy, but kept due to 289 * some issues in operation NuxeoDriveSetActiveFactories which freeze Jetty in unit tests when wanting 290 * to use standby strategy 291 */ 292 @Deprecated 293 public void setActiveFactories() { 294 topLevelFolderItemFactory = topLevelFolderItemFactoryRegistry.getActiveFactory( 295 activeTopLevelFolderItemFactoryRegistry.activeFactory); 296 fileSystemItemFactories = fileSystemItemFactoryRegistry.getOrderedActiveFactories( 297 activeFileSystemItemFactoryRegistry.activeFactories); 298 } 299 300 /*--------------------------- Protected ---------------------------------------*/ 301 /** 302 * Tries to adapt the given document as the top level {@link FolderItem}. If it doesn't match, iterates on the 303 * ordered contributed file system item factories until it finds one that matches and retrieves a non null 304 * {@link FileSystemItem} for the given document. A file system item factory matches if: 305 * <ul> 306 * <li>It is not bound to any docType nor facet (this is the case for the default factory contribution 307 * {@code defaultFileSystemItemFactory} bound to {@link DefaultFileSystemItemFactory})</li> 308 * <li>It is bound to a docType that matches the given doc's type</li> 309 * <li>It is bound to a facet that matches one of the given doc's facets</li> 310 * </ul> 311 */ 312 protected FileSystemItem getFileSystemItem(DocumentModel doc, boolean forceParentItem, FolderItem parentItem, 313 boolean includeDeleted, boolean relaxSyncRootConstraint, boolean getLockInfo) { 314 315 FileSystemItem fileSystemItem = null; 316 317 // Try the topLevelFolderItemFactory 318 if (forceParentItem) { 319 fileSystemItem = getTopLevelFolderItemFactory().getFileSystemItem(doc, parentItem, includeDeleted, 320 relaxSyncRootConstraint, getLockInfo); 321 } else { 322 fileSystemItem = getTopLevelFolderItemFactory().getFileSystemItem(doc, includeDeleted, 323 relaxSyncRootConstraint, getLockInfo); 324 } 325 if (fileSystemItem != null) { 326 return fileSystemItem; 327 } else { 328 if (log.isDebugEnabled()) { 329 log.debug(String.format( 330 "The topLevelFolderItemFactory is not able to adapt document %s as a FileSystemItem => trying fileSystemItemFactories.", 331 doc.getId())); 332 } 333 } 334 335 // Try the fileSystemItemFactories 336 FileSystemItemFactoryWrapper matchingFactory = null; 337 Iterator<FileSystemItemFactoryWrapper> factoriesIt = fileSystemItemFactories.iterator(); 338 while (factoriesIt.hasNext()) { 339 FileSystemItemFactoryWrapper factory = factoriesIt.next(); 340 if (log.isDebugEnabled()) { 341 log.debug(String.format("Trying to adapt document %s (path: %s) as a FileSystemItem with factory %s", 342 doc.getId(), doc.getPathAsString(), factory.getFactory().getName())); 343 } 344 if (generalFactoryMatches(factory) || docTypeFactoryMatches(factory, doc) 345 || facetFactoryMatches(factory, doc, relaxSyncRootConstraint)) { 346 matchingFactory = factory; 347 try { 348 if (forceParentItem) { 349 fileSystemItem = factory.getFactory().getFileSystemItem(doc, parentItem, includeDeleted, 350 relaxSyncRootConstraint, getLockInfo); 351 } else { 352 fileSystemItem = factory.getFactory().getFileSystemItem(doc, includeDeleted, 353 relaxSyncRootConstraint, getLockInfo); 354 } 355 } catch (RootlessItemException e) { 356 // Give more information in the exception message on the 357 // document whose adaption failed to recursively find the 358 // top level item. 359 throw new RootlessItemException(String.format( 360 "Cannot find path to registered top" + " level when adapting document " 361 + " '%s' (path: %s) with factory %s", 362 doc.getTitle(), doc.getPathAsString(), factory.getFactory().getName()), e); 363 } 364 if (fileSystemItem != null) { 365 if (log.isDebugEnabled()) { 366 log.debug(String.format("Adapted document '%s' (path: %s) to item with path %s with factory %s", 367 doc.getTitle(), doc.getPathAsString(), fileSystemItem.getPath(), 368 factory.getFactory().getName())); 369 } 370 return fileSystemItem; 371 } 372 } 373 } 374 375 if (matchingFactory == null) { 376 if (log.isDebugEnabled()) { 377 log.debug(String.format( 378 "None of the fileSystemItemFactories matches document %s => returning null. Please check the contributions to the following extension point: <extension target=\"org.nuxeo.drive.service.FileSystemItemAdapterService\" point=\"fileSystemItemFactory\">.", 379 doc.getId())); 380 } 381 } else { 382 if (log.isDebugEnabled()) { 383 log.debug(String.format( 384 "None of the fileSystemItemFactories matching document %s were able to adapt this document as a FileSystemItem => returning null.", 385 doc.getId())); 386 } 387 } 388 return fileSystemItem; 389 } 390 391 protected boolean generalFactoryMatches(FileSystemItemFactoryWrapper factory) { 392 boolean matches = StringUtils.isEmpty(factory.getDocType()) && StringUtils.isEmpty(factory.getFacet()); 393 if (log.isTraceEnabled() && matches) { 394 log.trace(String.format("General factory %s matches", factory)); 395 } 396 return matches; 397 } 398 399 protected boolean docTypeFactoryMatches(FileSystemItemFactoryWrapper factory, DocumentModel doc) { 400 boolean matches = !StringUtils.isEmpty(factory.getDocType()) && factory.getDocType().equals(doc.getType()); 401 if (log.isTraceEnabled() && matches) { 402 log.trace(String.format("DocType factory %s matches for doc %s (path: %s)", factory, doc.getId(), 403 doc.getPathAsString())); 404 } 405 return matches; 406 } 407 408 protected boolean facetFactoryMatches(FileSystemItemFactoryWrapper factory, DocumentModel doc, 409 boolean relaxSyncRootConstraint) { 410 if (!StringUtils.isEmpty(factory.getFacet())) { 411 for (String docFacet : doc.getFacets()) { 412 if (factory.getFacet().equals(docFacet)) { 413 // Handle synchronization root case 414 if (NuxeoDriveManagerImpl.NUXEO_DRIVE_FACET.equals(docFacet)) { 415 boolean matches = syncRootFactoryMatches(doc, relaxSyncRootConstraint); 416 if (log.isTraceEnabled() && matches) { 417 log.trace(String.format("Facet factory %s matches for doc %s (path: %s)", factory, 418 doc.getId(), doc.getPathAsString())); 419 } 420 return matches; 421 } else { 422 if (log.isTraceEnabled()) { 423 log.trace(String.format("Facet factory %s matches for doc %s (path: %s)", factory, 424 doc.getId(), doc.getPathAsString())); 425 } 426 return true; 427 } 428 } 429 } 430 } 431 return false; 432 } 433 434 @SuppressWarnings("unchecked") 435 protected boolean syncRootFactoryMatches(DocumentModel doc, boolean relaxSyncRootConstraint) { 436 String userName = doc.getCoreSession().getPrincipal().getName(); 437 List<Map<String, Object>> subscriptions = (List<Map<String, Object>>) doc.getPropertyValue( 438 NuxeoDriveManagerImpl.DRIVE_SUBSCRIPTIONS_PROPERTY); 439 for (Map<String, Object> subscription : subscriptions) { 440 if (Boolean.TRUE.equals(subscription.get("enabled"))) { 441 if (userName.equals(subscription.get("username"))) { 442 if (log.isTraceEnabled()) { 443 log.trace(String.format("Doc %s (path: %s) registered as a sync root for user %s", doc.getId(), 444 doc.getPathAsString(), userName)); 445 } 446 return true; 447 } 448 if (relaxSyncRootConstraint) { 449 if (log.isTraceEnabled()) { 450 log.trace(String.format( 451 "Doc %s (path: %s) registered as a sync root for at least one user (relaxSyncRootConstraint is true)", 452 doc.getId(), doc.getPathAsString())); 453 } 454 return true; 455 } 456 } 457 } 458 return false; 459 } 460 461}