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