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