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