001/* 002 * (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Antoine Taillefer <ataillefer@nuxeo.com> 016 */ 017package org.nuxeo.drive.service.impl; 018 019import java.util.ArrayList; 020import java.util.Iterator; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024 025import org.apache.commons.lang.StringUtils; 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028import org.nuxeo.drive.adapter.FileSystemItem; 029import org.nuxeo.drive.adapter.FolderItem; 030import org.nuxeo.drive.adapter.NuxeoDriveContribException; 031import org.nuxeo.drive.adapter.RootlessItemException; 032import org.nuxeo.drive.service.FileSystemItemAdapterService; 033import org.nuxeo.drive.service.FileSystemItemFactory; 034import org.nuxeo.drive.service.TopLevelFolderItemFactory; 035import org.nuxeo.drive.service.VirtualFolderItemFactory; 036import org.nuxeo.ecm.core.api.DocumentModel; 037import org.nuxeo.runtime.model.ComponentContext; 038import org.nuxeo.runtime.model.ComponentInstance; 039import org.nuxeo.runtime.model.DefaultComponent; 040 041/** 042 * Default implementation of the {@link FileSystemItemAdapterService}. 043 * 044 * @author Antoine Taillefer 045 */ 046public class FileSystemItemAdapterServiceImpl extends DefaultComponent implements FileSystemItemAdapterService { 047 048 private static final Log log = LogFactory.getLog(FileSystemItemAdapterServiceImpl.class); 049 050 public static final String FILE_SYSTEM_ITEM_FACTORY_EP = "fileSystemItemFactory"; 051 052 public static final String TOP_LEVEL_FOLDER_ITEM_FACTORY_EP = "topLevelFolderItemFactory"; 053 054 public static final String ACTIVE_FILE_SYSTEM_ITEM_FACTORIES_EP = "activeFileSystemItemFactories"; 055 056 protected TopLevelFolderItemFactoryRegistry topLevelFolderItemFactoryRegistry; 057 058 protected FileSystemItemFactoryRegistry fileSystemItemFactoryRegistry; 059 060 protected ActiveTopLevelFolderItemFactoryRegistry activeTopLevelFolderItemFactoryRegistry; 061 062 protected ActiveFileSystemItemFactoryRegistry activeFileSystemItemFactoryRegistry; 063 064 protected TopLevelFolderItemFactory topLevelFolderItemFactory; 065 066 protected List<FileSystemItemFactoryWrapper> fileSystemItemFactories; 067 068 /*------------------------ DefaultComponent -----------------------------*/ 069 @Override 070 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 071 if (FILE_SYSTEM_ITEM_FACTORY_EP.equals(extensionPoint)) { 072 fileSystemItemFactoryRegistry.addContribution((FileSystemItemFactoryDescriptor) contribution); 073 } else if (TOP_LEVEL_FOLDER_ITEM_FACTORY_EP.equals(extensionPoint)) { 074 topLevelFolderItemFactoryRegistry.addContribution((TopLevelFolderItemFactoryDescriptor) contribution); 075 } else if (ACTIVE_FILE_SYSTEM_ITEM_FACTORIES_EP.equals(extensionPoint)) { 076 if (contribution instanceof ActiveTopLevelFolderItemFactoryDescriptor) { 077 activeTopLevelFolderItemFactoryRegistry.addContribution((ActiveTopLevelFolderItemFactoryDescriptor) contribution); 078 } else if (contribution instanceof ActiveFileSystemItemFactoriesDescriptor) { 079 activeFileSystemItemFactoryRegistry.addContribution((ActiveFileSystemItemFactoriesDescriptor) contribution); 080 } 081 } else { 082 log.error("Unknown extension point " + extensionPoint); 083 } 084 } 085 086 @Override 087 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 088 if (FILE_SYSTEM_ITEM_FACTORY_EP.equals(extensionPoint)) { 089 fileSystemItemFactoryRegistry.removeContribution((FileSystemItemFactoryDescriptor) contribution); 090 } else if (TOP_LEVEL_FOLDER_ITEM_FACTORY_EP.equals(extensionPoint)) { 091 topLevelFolderItemFactoryRegistry.removeContribution((TopLevelFolderItemFactoryDescriptor) contribution); 092 } else if (ACTIVE_FILE_SYSTEM_ITEM_FACTORIES_EP.equals(extensionPoint)) { 093 if (contribution instanceof ActiveTopLevelFolderItemFactoryDescriptor) { 094 activeTopLevelFolderItemFactoryRegistry.removeContribution((ActiveTopLevelFolderItemFactoryDescriptor) contribution); 095 } else if (contribution instanceof ActiveFileSystemItemFactoriesDescriptor) { 096 activeFileSystemItemFactoryRegistry.removeContribution((ActiveFileSystemItemFactoriesDescriptor) contribution); 097 } 098 } else { 099 log.error("Unknown extension point " + extensionPoint); 100 } 101 } 102 103 @Override 104 public void activate(ComponentContext context) { 105 fileSystemItemFactoryRegistry = new FileSystemItemFactoryRegistry(); 106 topLevelFolderItemFactoryRegistry = new TopLevelFolderItemFactoryRegistry(); 107 activeTopLevelFolderItemFactoryRegistry = new ActiveTopLevelFolderItemFactoryRegistry(); 108 activeFileSystemItemFactoryRegistry = new ActiveFileSystemItemFactoryRegistry(); 109 fileSystemItemFactories = new ArrayList<FileSystemItemFactoryWrapper>(); 110 } 111 112 @Override 113 public void deactivate(ComponentContext context) { 114 super.deactivate(context); 115 fileSystemItemFactoryRegistry = null; 116 topLevelFolderItemFactoryRegistry = null; 117 activeTopLevelFolderItemFactoryRegistry = null; 118 activeFileSystemItemFactoryRegistry = null; 119 fileSystemItemFactories = null; 120 } 121 122 /** 123 * Sorts the contributed factories according to their order. 124 */ 125 @Override 126 public void applicationStarted(ComponentContext context) { 127 setActiveFactories(); 128 } 129 130 /*------------------------ FileSystemItemAdapterService -----------------------*/ 131 @Override 132 public FileSystemItem getFileSystemItem(DocumentModel doc) { 133 return getFileSystemItem(doc, false, null, false, false); 134 } 135 136 @Override 137 public FileSystemItem getFileSystemItem(DocumentModel doc, boolean includeDeleted) { 138 return getFileSystemItem(doc, false, null, includeDeleted, false); 139 } 140 141 @Override 142 public FileSystemItem getFileSystemItem(DocumentModel doc, boolean includeDeleted, boolean relaxSyncRootConstraint) { 143 return getFileSystemItem(doc, false, null, includeDeleted, relaxSyncRootConstraint); 144 } 145 146 @Override 147 public FileSystemItem getFileSystemItem(DocumentModel doc, FolderItem parentItem) { 148 return getFileSystemItem(doc, true, parentItem, false, false); 149 } 150 151 @Override 152 public FileSystemItem getFileSystemItem(DocumentModel doc, FolderItem parentItem, boolean includeDeleted) { 153 return getFileSystemItem(doc, true, parentItem, includeDeleted, false); 154 } 155 156 @Override 157 public FileSystemItem getFileSystemItem(DocumentModel doc, FolderItem parentItem, boolean includeDeleted, 158 boolean relaxSyncRootConstraint) { 159 return getFileSystemItem(doc, true, parentItem, includeDeleted, relaxSyncRootConstraint); 160 } 161 162 /** 163 * Iterates on the ordered contributed file system item factories until if finds one that can handle the given 164 * {@link FileSystemItem} id. 165 */ 166 @Override 167 public FileSystemItemFactory getFileSystemItemFactoryForId(String id) { 168 Iterator<FileSystemItemFactoryWrapper> factoriesIt = fileSystemItemFactories.iterator(); 169 while (factoriesIt.hasNext()) { 170 FileSystemItemFactoryWrapper factoryWrapper = factoriesIt.next(); 171 FileSystemItemFactory factory = factoryWrapper.getFactory(); 172 if (factory.canHandleFileSystemItemId(id)) { 173 return factory; 174 } 175 } 176 // No fileSystemItemFactory found, try the topLevelFolderItemFactory 177 if (getTopLevelFolderItemFactory().canHandleFileSystemItemId(id)) { 178 return getTopLevelFolderItemFactory(); 179 } 180 throw new NuxeoDriveContribException( 181 String.format( 182 "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.", 183 id)); 184 } 185 186 @Override 187 public TopLevelFolderItemFactory getTopLevelFolderItemFactory() { 188 if (topLevelFolderItemFactory == null) { 189 throw new NuxeoDriveContribException( 190 "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\">."); 191 } 192 return topLevelFolderItemFactory; 193 } 194 195 @Override 196 public VirtualFolderItemFactory getVirtualFolderItemFactory(String factoryName) { 197 FileSystemItemFactory factory = getFileSystemItemFactory(factoryName); 198 if (factory == null) { 199 throw new NuxeoDriveContribException( 200 String.format( 201 "No factory named %s. Please check the contributions to the following extension point: <extension target=\"org.nuxeo.drive.service.FileSystemItemAdapterService\" point=\"fileSystemItemFactory\">.", 202 factoryName)); 203 } 204 if (!(factory instanceof VirtualFolderItemFactory)) { 205 throw new NuxeoDriveContribException(String.format( 206 "Factory class %s for factory %s is not a VirtualFolderItemFactory.", factory.getClass().getName(), 207 factory.getName())); 208 } 209 return (VirtualFolderItemFactory) factory; 210 } 211 212 @Override 213 public Set<String> getActiveFileSystemItemFactories() { 214 if (activeFileSystemItemFactoryRegistry.activeFactories.isEmpty()) { 215 throw new NuxeoDriveContribException( 216 "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."); 217 } 218 return activeFileSystemItemFactoryRegistry.activeFactories; 219 } 220 221 /*------------------------- For test purpose ----------------------------------*/ 222 public Map<String, FileSystemItemFactoryDescriptor> getFileSystemItemFactoryDescriptors() { 223 return fileSystemItemFactoryRegistry.factoryDescriptors; 224 } 225 226 public List<FileSystemItemFactoryWrapper> getFileSystemItemFactories() { 227 return fileSystemItemFactories; 228 } 229 230 public FileSystemItemFactory getFileSystemItemFactory(String name) { 231 for (FileSystemItemFactoryWrapper factoryWrapper : fileSystemItemFactories) { 232 FileSystemItemFactory factory = factoryWrapper.getFactory(); 233 if (name.equals(factory.getName())) { 234 return factory; 235 } 236 } 237 if (log.isDebugEnabled()) { 238 log.debug(String.format("No fileSystemItemFactory named %s, returning null.", name)); 239 } 240 return null; 241 } 242 243 /*--------------------------- Protected ---------------------------------------*/ 244 protected void setActiveFactories() { 245 topLevelFolderItemFactory = topLevelFolderItemFactoryRegistry.getActiveFactory(activeTopLevelFolderItemFactoryRegistry.activeFactory); 246 fileSystemItemFactories = fileSystemItemFactoryRegistry.getOrderedActiveFactories(activeFileSystemItemFactoryRegistry.activeFactories); 247 } 248 249 /** 250 * Tries to adapt the given document as the top level {@link FolderItem}. If it doesn't match, iterates on the 251 * ordered contributed file system item factories until it finds one that matches and retrieves a non null 252 * {@link FileSystemItem} for the given document. A file system item factory matches if: 253 * <ul> 254 * <li>It is not bound to any docType nor facet (this is the case for the default factory contribution 255 * {@code defaultFileSystemItemFactory} bound to {@link DefaultFileSystemItemFactory})</li> 256 * <li>It is bound to a docType that matches the given doc's type</li> 257 * <li>It is bound to a facet that matches one of the given doc's facets</li> 258 * </ul> 259 */ 260 protected FileSystemItem getFileSystemItem(DocumentModel doc, boolean forceParentItem, FolderItem parentItem, 261 boolean includeDeleted, boolean relaxSyncRootConstraint) { 262 263 FileSystemItem fileSystemItem = null; 264 265 // Try the topLevelFolderItemFactory 266 if (forceParentItem) { 267 fileSystemItem = getTopLevelFolderItemFactory().getFileSystemItem(doc, parentItem, includeDeleted); 268 } else { 269 fileSystemItem = getTopLevelFolderItemFactory().getFileSystemItem(doc, includeDeleted); 270 } 271 if (fileSystemItem != null) { 272 return fileSystemItem; 273 } else { 274 if (log.isDebugEnabled()) { 275 log.debug(String.format( 276 "The topLevelFolderItemFactory is not able to adapt document %s as a FileSystemItem => trying fileSystemItemFactories.", 277 doc.getId())); 278 } 279 } 280 281 // Try the fileSystemItemFactories 282 FileSystemItemFactoryWrapper matchingFactory = null; 283 Iterator<FileSystemItemFactoryWrapper> factoriesIt = fileSystemItemFactories.iterator(); 284 while (factoriesIt.hasNext()) { 285 FileSystemItemFactoryWrapper factory = factoriesIt.next(); 286 if (log.isDebugEnabled()) { 287 log.debug(String.format("Trying to adapt document %s (path: %s) as a FileSystemItem with factory %s", 288 doc.getId(), doc.getPathAsString(), factory.getFactory().getName())); 289 } 290 if (generalFactoryMatches(factory) || docTypeFactoryMatches(factory, doc) 291 || facetFactoryMatches(factory, doc, relaxSyncRootConstraint)) { 292 matchingFactory = factory; 293 try { 294 if (forceParentItem) { 295 fileSystemItem = factory.getFactory().getFileSystemItem(doc, parentItem, includeDeleted, 296 relaxSyncRootConstraint); 297 } else { 298 fileSystemItem = factory.getFactory().getFileSystemItem(doc, includeDeleted, 299 relaxSyncRootConstraint); 300 } 301 } catch (RootlessItemException e) { 302 // Give more information in the exception message on the 303 // document whose adaption failed to recursively find the 304 // top level item. 305 throw new RootlessItemException(String.format("Cannot find path to registered top" 306 + " level when adapting document " + " '%s' (path: %s) with factory %s", doc.getTitle(), 307 doc.getPathAsString(), factory.getFactory().getName()), e); 308 } 309 if (fileSystemItem != null) { 310 if (log.isDebugEnabled()) { 311 log.debug(String.format( 312 "Adapted document '%s' (path: %s) to item with path %s with factory %s", 313 doc.getTitle(), doc.getPathAsString(), fileSystemItem.getPath(), 314 factory.getFactory().getName())); 315 } 316 return fileSystemItem; 317 } 318 } 319 } 320 321 if (matchingFactory == null) { 322 if (log.isDebugEnabled()) { 323 log.debug(String.format( 324 "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\">.", 325 doc.getId())); 326 } 327 } else { 328 if (log.isDebugEnabled()) { 329 log.debug(String.format( 330 "None of the fileSystemItemFactories matching document %s were able to adapt this document as a FileSystemItem => returning null.", 331 doc.getId())); 332 } 333 } 334 return fileSystemItem; 335 } 336 337 protected boolean generalFactoryMatches(FileSystemItemFactoryWrapper factory) { 338 boolean matches = StringUtils.isEmpty(factory.getDocType()) && StringUtils.isEmpty(factory.getFacet()); 339 if (log.isTraceEnabled() && matches) { 340 log.trace(String.format("General factory %s matches", factory)); 341 } 342 return matches; 343 } 344 345 protected boolean docTypeFactoryMatches(FileSystemItemFactoryWrapper factory, DocumentModel doc) { 346 boolean matches = !StringUtils.isEmpty(factory.getDocType()) && factory.getDocType().equals(doc.getType()); 347 if (log.isTraceEnabled() && matches) { 348 log.trace(String.format("DocType factory %s matches for doc %s (path: %s)", factory, doc.getId(), 349 doc.getPathAsString())); 350 } 351 return matches; 352 } 353 354 protected boolean facetFactoryMatches(FileSystemItemFactoryWrapper factory, DocumentModel doc, 355 boolean relaxSyncRootConstraint) { 356 if (!StringUtils.isEmpty(factory.getFacet())) { 357 for (String docFacet : doc.getFacets()) { 358 if (factory.getFacet().equals(docFacet)) { 359 // Handle synchronization root case 360 if (NuxeoDriveManagerImpl.NUXEO_DRIVE_FACET.equals(docFacet)) { 361 boolean matches = syncRootFactoryMatches(doc, relaxSyncRootConstraint); 362 if (log.isTraceEnabled() && matches) { 363 log.trace(String.format("Facet factory %s matches for doc %s (path: %s)", factory, 364 doc.getId(), doc.getPathAsString())); 365 } 366 return matches; 367 } else { 368 if (log.isTraceEnabled()) { 369 log.trace(String.format("Facet factory %s matches for doc %s (path: %s)", factory, 370 doc.getId(), doc.getPathAsString())); 371 } 372 return true; 373 } 374 } 375 } 376 } 377 return false; 378 } 379 380 @SuppressWarnings("unchecked") 381 protected boolean syncRootFactoryMatches(DocumentModel doc, boolean relaxSyncRootConstraint) { 382 String userName = doc.getCoreSession().getPrincipal().getName(); 383 List<Map<String, Object>> subscriptions = (List<Map<String, Object>>) doc.getPropertyValue(NuxeoDriveManagerImpl.DRIVE_SUBSCRIPTIONS_PROPERTY); 384 for (Map<String, Object> subscription : subscriptions) { 385 if (Boolean.TRUE.equals(subscription.get("enabled")) 386 && (userName.equals(subscription.get("username")) || relaxSyncRootConstraint)) { 387 if (log.isTraceEnabled()) { 388 log.trace(String.format("Doc %s (path: %s) registered as a sync root for user %s", doc.getId(), 389 doc.getPathAsString(), userName)); 390 } 391 return true; 392 } 393 } 394 return false; 395 } 396 397}