001/* 002 * (C) Copyright 2002 - 2006 Nuxeo SARL <http://nuxeo.com> and others 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * Nuxeo - initial API and implementation 011 * 012 * 013 * $Id: Registry.java 2531 2006-09-04 23:01:57Z janguenot $ 014 */ 015 016package org.nuxeo.ecm.platform.filemanager.service; 017 018import java.io.IOException; 019import java.security.Principal; 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.LinkedList; 024import java.util.List; 025import java.util.Locale; 026import java.util.Map; 027 028import org.apache.commons.lang.StringUtils; 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.nuxeo.ecm.core.api.Blob; 032import org.nuxeo.ecm.core.api.CoreInstance; 033import org.nuxeo.ecm.core.api.CoreSession; 034import org.nuxeo.ecm.core.api.DocumentLocation; 035import org.nuxeo.ecm.core.api.DocumentModel; 036import org.nuxeo.ecm.core.api.DocumentModelList; 037import org.nuxeo.ecm.core.api.DocumentSecurityException; 038import org.nuxeo.ecm.core.api.PathRef; 039import org.nuxeo.ecm.core.api.VersioningOption; 040import org.nuxeo.ecm.core.api.impl.DocumentLocationImpl; 041import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; 042import org.nuxeo.ecm.core.api.pathsegment.PathSegmentService; 043import org.nuxeo.ecm.core.api.repository.RepositoryManager; 044import org.nuxeo.ecm.core.api.security.SecurityConstants; 045import org.nuxeo.ecm.platform.filemanager.api.FileManager; 046import org.nuxeo.ecm.platform.filemanager.service.extension.CreationContainerListProvider; 047import org.nuxeo.ecm.platform.filemanager.service.extension.CreationContainerListProviderDescriptor; 048import org.nuxeo.ecm.platform.filemanager.service.extension.FileImporter; 049import org.nuxeo.ecm.platform.filemanager.service.extension.FileImporterDescriptor; 050import org.nuxeo.ecm.platform.filemanager.service.extension.FolderImporter; 051import org.nuxeo.ecm.platform.filemanager.service.extension.FolderImporterDescriptor; 052import org.nuxeo.ecm.platform.filemanager.service.extension.UnicityExtension; 053import org.nuxeo.ecm.platform.filemanager.service.extension.VersioningDescriptor; 054import org.nuxeo.ecm.platform.filemanager.utils.FileManagerUtils; 055import org.nuxeo.ecm.platform.mimetype.MimetypeDetectionException; 056import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry; 057import org.nuxeo.ecm.platform.types.TypeManager; 058import org.nuxeo.runtime.api.Framework; 059import org.nuxeo.runtime.model.ComponentName; 060import org.nuxeo.runtime.model.DefaultComponent; 061import org.nuxeo.runtime.model.Extension; 062 063/** 064 * FileManager registry service. 065 * <p> 066 * This is the component to request to perform transformations. See API. 067 * 068 * @author <a href="mailto:andreas.kalogeropoulos@nuxeo.com">Andreas Kalogeropoulos</a> 069 */ 070public class FileManagerService extends DefaultComponent implements FileManager { 071 072 public static final ComponentName NAME = new ComponentName( 073 "org.nuxeo.ecm.platform.filemanager.service.FileManagerService"); 074 075 public static final String DEFAULT_FOLDER_TYPE_NAME = "Folder"; 076 077 // TODO: OG: we should use an overridable query model instead of hardcoding 078 // the NXQL query 079 public static final String QUERY = "SELECT * FROM Document WHERE file:content/digest = '%s'"; 080 081 public static final int MAX = 15; 082 083 private static final Log log = LogFactory.getLog(FileManagerService.class); 084 085 private final Map<String, FileImporter> fileImporters; 086 087 private final List<FolderImporter> folderImporters; 088 089 private final List<CreationContainerListProvider> creationContainerListProviders; 090 091 private List<String> fieldsXPath = new ArrayList<String>(); 092 093 private MimetypeRegistry mimeService; 094 095 private boolean unicityEnabled = false; 096 097 private String digestAlgorithm = "sha-256"; 098 099 private boolean computeDigest = false; 100 101 public static final VersioningOption DEF_VERSIONING_OPTION = VersioningOption.MINOR; 102 103 public static final boolean DEF_VERSIONING_AFTER_ADD = false; 104 105 /** 106 * @since 5.7 107 */ 108 private VersioningOption defaultVersioningOption = DEF_VERSIONING_OPTION; 109 110 /** 111 * @since 5.7 112 */ 113 private boolean versioningAfterAdd = DEF_VERSIONING_AFTER_ADD; 114 115 private TypeManager typeService; 116 117 public FileManagerService() { 118 fileImporters = new HashMap<String, FileImporter>(); 119 folderImporters = new LinkedList<FolderImporter>(); 120 creationContainerListProviders = new LinkedList<CreationContainerListProvider>(); 121 } 122 123 private MimetypeRegistry getMimeService() { 124 if (mimeService == null) { 125 mimeService = Framework.getService(MimetypeRegistry.class); 126 } 127 return mimeService; 128 } 129 130 private TypeManager getTypeService() { 131 if (typeService == null) { 132 typeService = Framework.getService(TypeManager.class); 133 } 134 return typeService; 135 } 136 137 private Blob checkMimeType(Blob blob, String fullname) { 138 final String mimeType = blob.getMimeType(); 139 if (mimeType != null && !mimeType.isEmpty() && !mimeType.equals("application/octet-stream") 140 && !mimeType.equals("application/octetstream")) { 141 return blob; 142 } 143 String filename = FileManagerUtils.fetchFileName(fullname); 144 blob = getMimeService().updateMimetype(blob, filename); 145 return blob; 146 } 147 148 public DocumentModel createFolder(CoreSession documentManager, String fullname, String path) 149 throws IOException { 150 151 if (folderImporters.isEmpty()) { 152 return defaultCreateFolder(documentManager, fullname, path); 153 } else { 154 // use the last registered folder importer 155 FolderImporter folderImporter = folderImporters.get(folderImporters.size() - 1); 156 return folderImporter.create(documentManager, fullname, path, true, getTypeService()); 157 } 158 } 159 160 public DocumentModel defaultCreateFolder(CoreSession documentManager, String fullname, String path) 161 { 162 return defaultCreateFolder(documentManager, fullname, path, DEFAULT_FOLDER_TYPE_NAME, true); 163 } 164 165 public DocumentModel defaultCreateFolder(CoreSession documentManager, String fullname, String path, 166 String containerTypeName, boolean checkAllowedSubTypes) { 167 168 // Fetching filename 169 String title = FileManagerUtils.fetchFileName(fullname); 170 171 // Looking if an existing Folder with the same filename exists. 172 DocumentModel docModel = FileManagerUtils.getExistingDocByTitle(documentManager, path, title); 173 174 if (docModel == null) { 175 // check permissions 176 PathRef containerRef = new PathRef(path); 177 if (!documentManager.hasPermission(containerRef, SecurityConstants.READ_PROPERTIES) 178 || !documentManager.hasPermission(containerRef, SecurityConstants.ADD_CHILDREN)) { 179 throw new DocumentSecurityException("Not enough rights to create folder"); 180 } 181 182 // check allowed sub types 183 DocumentModel container = documentManager.getDocument(containerRef); 184 if (checkAllowedSubTypes 185 && !getTypeService().isAllowedSubType(containerTypeName, container.getType(), container)) { 186 // cannot create document file here 187 // TODO: we should better raise a dedicated exception to be 188 // catched by the FileManageActionsBean instead of returning 189 // null 190 return null; 191 } 192 193 PathSegmentService pss = Framework.getService(PathSegmentService.class); 194 docModel = documentManager.createDocumentModel(containerTypeName); 195 docModel.setProperty("dublincore", "title", title); 196 197 // writing changes 198 docModel.setPathInfo(path, pss.generatePathSegment(docModel)); 199 docModel = documentManager.createDocument(docModel); 200 documentManager.save(); 201 202 log.debug("Created container: " + docModel.getName() + " with type " + containerTypeName); 203 } 204 return docModel; 205 } 206 207 public DocumentModel createDocumentFromBlob(CoreSession documentManager, Blob input, String path, 208 boolean overwrite, String fullName) throws IOException { 209 210 // check mime type to be able to select the best importer plugin 211 input = checkMimeType(input, fullName); 212 213 List<FileImporter> importers = new ArrayList<FileImporter>(fileImporters.values()); 214 Collections.sort(importers); 215 String normalizedMimeType = getMimeService().getMimetypeEntryByMimeType(input.getMimeType()).getNormalized(); 216 for (FileImporter importer : importers) { 217 if (importer.isEnabled() && (importer.matches(normalizedMimeType) || importer.matches(input.getMimeType()))) { 218 DocumentModel doc = importer.create(documentManager, input, path, overwrite, fullName, getTypeService()); 219 if (doc != null) { 220 return doc; 221 } 222 } 223 } 224 return null; 225 } 226 227 public DocumentModel updateDocumentFromBlob(CoreSession documentManager, Blob input, String path, String fullName) 228 { 229 String filename = FileManagerUtils.fetchFileName(fullName); 230 DocumentModel doc = FileManagerUtils.getExistingDocByFileName(documentManager, path, filename); 231 if (doc != null) { 232 doc.setProperty("file", "content", input); 233 234 documentManager.saveDocument(doc); 235 documentManager.save(); 236 237 log.debug("Updated the document: " + doc.getName()); 238 } 239 return doc; 240 } 241 242 public FileImporter getPluginByName(String name) { 243 return fileImporters.get(name); 244 } 245 246 @Override 247 public void registerExtension(Extension extension) { 248 if (extension.getExtensionPoint().equals("plugins")) { 249 Object[] contribs = extension.getContributions(); 250 for (Object contrib : contribs) { 251 if (contrib instanceof FileImporterDescriptor) { 252 registerFileImporter((FileImporterDescriptor) contrib, extension); 253 } else if (contrib instanceof FolderImporterDescriptor) { 254 registerFolderImporter((FolderImporterDescriptor) contrib, extension); 255 } else if (contrib instanceof CreationContainerListProviderDescriptor) { 256 registerCreationContainerListProvider((CreationContainerListProviderDescriptor) contrib, extension); 257 } 258 } 259 } else if (extension.getExtensionPoint().equals("unicity")) { 260 Object[] contribs = extension.getContributions(); 261 for (Object contrib : contribs) { 262 if (contrib instanceof UnicityExtension) { 263 registerUnicityOptions((UnicityExtension) contrib, extension); 264 } 265 } 266 } else if (extension.getExtensionPoint().equals("versioning")) { 267 Object[] contribs = extension.getContributions(); 268 for (Object contrib : contribs) { 269 if (contrib instanceof VersioningDescriptor) { 270 VersioningDescriptor descr = (VersioningDescriptor) contrib; 271 String defver = descr.defaultVersioningOption; 272 if (!StringUtils.isBlank(defver)) { 273 try { 274 defaultVersioningOption = VersioningOption.valueOf(defver.toUpperCase(Locale.ENGLISH)); 275 } catch (IllegalArgumentException e) { 276 log.warn(String.format("Illegal versioning option: %s, using %s instead", defver, 277 DEF_VERSIONING_OPTION)); 278 defaultVersioningOption = DEF_VERSIONING_OPTION; 279 } 280 } 281 Boolean veradd = descr.versionAfterAdd; 282 if (veradd != null) { 283 versioningAfterAdd = veradd.booleanValue(); 284 } 285 } 286 } 287 } else { 288 log.warn(String.format("Unknown contribution %s: ignored", extension.getExtensionPoint())); 289 } 290 } 291 292 @Override 293 public void unregisterExtension(Extension extension) { 294 if (extension.getExtensionPoint().equals("plugins")) { 295 Object[] contribs = extension.getContributions(); 296 297 for (Object contrib : contribs) { 298 if (contrib instanceof FileImporterDescriptor) { 299 unregisterFileImporter((FileImporterDescriptor) contrib); 300 } else if (contrib instanceof FolderImporterDescriptor) { 301 unregisterFolderImporter((FolderImporterDescriptor) contrib); 302 } else if (contrib instanceof CreationContainerListProviderDescriptor) { 303 unregisterCreationContainerListProvider((CreationContainerListProviderDescriptor) contrib); 304 } 305 } 306 } else if (extension.getExtensionPoint().equals("unicity")) { 307 308 } else if (extension.getExtensionPoint().equals("versioning")) { 309 // set to default value 310 defaultVersioningOption = DEF_VERSIONING_OPTION; 311 versioningAfterAdd = DEF_VERSIONING_AFTER_ADD; 312 } else { 313 log.warn(String.format("Unknown contribution %s: ignored", extension.getExtensionPoint())); 314 } 315 } 316 317 private void registerUnicityOptions(UnicityExtension unicityExtension, Extension extension) { 318 if (unicityExtension.getAlgo() != null) { 319 digestAlgorithm = unicityExtension.getAlgo(); 320 } 321 if (unicityExtension.getEnabled() != null) { 322 unicityEnabled = unicityExtension.getEnabled().booleanValue(); 323 } 324 if (unicityExtension.getFields() != null) { 325 fieldsXPath = unicityExtension.getFields(); 326 } else { 327 fieldsXPath.add("file:content"); 328 } 329 if (unicityExtension.getComputeDigest() != null) { 330 computeDigest = unicityExtension.getComputeDigest().booleanValue(); 331 } 332 } 333 334 private void registerFileImporter(FileImporterDescriptor pluginExtension, Extension extension) { 335 String name = pluginExtension.getName(); 336 if (name == null) { 337 log.error("Cannot register file importer without a name"); 338 return; 339 } 340 341 String className = pluginExtension.getClassName(); 342 if (fileImporters.containsKey(name)) { 343 log.info("Overriding file importer plugin " + name); 344 FileImporter oldPlugin = fileImporters.get(name); 345 FileImporter newPlugin; 346 try { 347 newPlugin = className != null ? (FileImporter) extension.getContext().loadClass(className).newInstance() 348 : oldPlugin; 349 } catch (ReflectiveOperationException e) { 350 throw new RuntimeException(e); 351 } 352 if (pluginExtension.isMerge()) { 353 newPlugin = mergeFileImporters(oldPlugin, newPlugin, pluginExtension); 354 } else { 355 newPlugin = fillImporterWithDescriptor(newPlugin, pluginExtension); 356 } 357 fileImporters.put(name, newPlugin); 358 log.info("Registered file importer " + name); 359 } else if (className != null) { 360 FileImporter plugin; 361 try { 362 plugin = (FileImporter) extension.getContext().loadClass(className).newInstance(); 363 } catch (ReflectiveOperationException e) { 364 throw new RuntimeException(e); 365 } 366 plugin = fillImporterWithDescriptor(plugin, pluginExtension); 367 fileImporters.put(name, plugin); 368 log.info("Registered file importer " + name); 369 } else { 370 log.info("Unable to register file importer " + name + ", className is null or plugin is not yet registered"); 371 } 372 } 373 374 private FileImporter mergeFileImporters(FileImporter oldPlugin, FileImporter newPlugin, FileImporterDescriptor desc) { 375 List<String> filters = desc.getFilters(); 376 if (filters != null && !filters.isEmpty()) { 377 List<String> oldFilters = oldPlugin.getFilters(); 378 oldFilters.addAll(filters); 379 newPlugin.setFilters(oldFilters); 380 } 381 newPlugin.setName(desc.getName()); 382 String docType = desc.getDocType(); 383 if (docType != null) { 384 newPlugin.setDocType(docType); 385 } 386 newPlugin.setFileManagerService(this); 387 newPlugin.setEnabled(desc.isEnabled()); 388 Integer order = desc.getOrder(); 389 if (order != null) { 390 newPlugin.setOrder(desc.getOrder()); 391 } 392 return newPlugin; 393 } 394 395 private FileImporter fillImporterWithDescriptor(FileImporter fileImporter, FileImporterDescriptor desc) { 396 List<String> filters = desc.getFilters(); 397 if (filters != null && !filters.isEmpty()) { 398 fileImporter.setFilters(filters); 399 } 400 fileImporter.setName(desc.getName()); 401 fileImporter.setDocType(desc.getDocType()); 402 fileImporter.setFileManagerService(this); 403 fileImporter.setEnabled(desc.isEnabled()); 404 fileImporter.setOrder(desc.getOrder()); 405 return fileImporter; 406 } 407 408 private void unregisterFileImporter(FileImporterDescriptor pluginExtension) { 409 String name = pluginExtension.getName(); 410 fileImporters.remove(name); 411 log.info("unregistered file importer: " + name); 412 } 413 414 private void registerFolderImporter(FolderImporterDescriptor folderImporterDescriptor, Extension extension) { 415 416 String name = folderImporterDescriptor.getName(); 417 String className = folderImporterDescriptor.getClassName(); 418 419 FolderImporter folderImporter; 420 try { 421 folderImporter = (FolderImporter) extension.getContext().loadClass(className).newInstance(); 422 } catch (ReflectiveOperationException e) { 423 throw new RuntimeException(e); 424 } 425 folderImporter.setName(name); 426 folderImporter.setFileManagerService(this); 427 folderImporters.add(folderImporter); 428 log.info("registered folder importer: " + name); 429 } 430 431 private void unregisterFolderImporter(FolderImporterDescriptor folderImporterDescriptor) { 432 String name = folderImporterDescriptor.getName(); 433 FolderImporter folderImporterToRemove = null; 434 for (FolderImporter folderImporter : folderImporters) { 435 if (name.equals(folderImporter.getName())) { 436 folderImporterToRemove = folderImporter; 437 } 438 } 439 if (folderImporterToRemove != null) { 440 folderImporters.remove(folderImporterToRemove); 441 } 442 log.info("unregistered folder importer: " + name); 443 } 444 445 private void registerCreationContainerListProvider( 446 CreationContainerListProviderDescriptor ccListProviderDescriptor, Extension extension) { 447 448 String name = ccListProviderDescriptor.getName(); 449 String[] docTypes = ccListProviderDescriptor.getDocTypes(); 450 String className = ccListProviderDescriptor.getClassName(); 451 452 CreationContainerListProvider provider; 453 try { 454 provider = (CreationContainerListProvider) extension.getContext().loadClass(className).newInstance(); 455 } catch (ReflectiveOperationException e) { 456 throw new RuntimeException(e); 457 } 458 provider.setName(name); 459 provider.setDocTypes(docTypes); 460 if (creationContainerListProviders.contains(provider)) { 461 // equality and containment tests are based on unique names 462 creationContainerListProviders.remove(provider); 463 } 464 // add the new provider at the beginning of the list 465 creationContainerListProviders.add(0, provider); 466 log.info("registered creationContaineterList provider: " + name); 467 } 468 469 private void unregisterCreationContainerListProvider( 470 CreationContainerListProviderDescriptor ccListProviderDescriptor) { 471 String name = ccListProviderDescriptor.getName(); 472 CreationContainerListProvider providerToRemove = null; 473 for (CreationContainerListProvider provider : creationContainerListProviders) { 474 if (name.equals(provider.getName())) { 475 providerToRemove = provider; 476 break; 477 } 478 } 479 if (providerToRemove != null) { 480 creationContainerListProviders.remove(providerToRemove); 481 } 482 log.info("unregistered creationContaineterList provider: " + name); 483 } 484 485 public List<DocumentLocation> findExistingDocumentWithFile(CoreSession documentManager, String path, String digest, 486 Principal principal) { 487 String nxql = String.format(QUERY, digest); 488 DocumentModelList documentModelList = documentManager.query(nxql, MAX); 489 List<DocumentLocation> docLocationList = new ArrayList<DocumentLocation>(documentModelList.size()); 490 for (DocumentModel documentModel : documentModelList) { 491 docLocationList.add(new DocumentLocationImpl(documentModel)); 492 } 493 return docLocationList; 494 } 495 496 public boolean isUnicityEnabled() { 497 return unicityEnabled; 498 } 499 500 public boolean isDigestComputingEnabled() { 501 return computeDigest; 502 } 503 504 public List<String> getFields() { 505 return fieldsXPath; 506 } 507 508 public DocumentModelList getCreationContainers(Principal principal, String docType) { 509 DocumentModelList containers = new DocumentModelListImpl(); 510 RepositoryManager repositoryManager = Framework.getLocalService(RepositoryManager.class); 511 for (String repositoryName : repositoryManager.getRepositoryNames()) { 512 try (CoreSession session = CoreInstance.openCoreSession(repositoryName, principal)) { 513 containers.addAll(getCreationContainers(session, docType)); 514 } 515 } 516 return containers; 517 } 518 519 public DocumentModelList getCreationContainers(CoreSession documentManager, String docType) { 520 for (CreationContainerListProvider provider : creationContainerListProviders) { 521 if (provider.accept(docType)) { 522 return provider.getCreationContainerList(documentManager, docType); 523 } 524 } 525 return new DocumentModelListImpl(); 526 } 527 528 public String getDigestAlgorithm() { 529 return digestAlgorithm; 530 } 531 532 @Override 533 public VersioningOption getVersioningOption() { 534 return defaultVersioningOption; 535 } 536 537 @Override 538 public boolean doVersioningAfterAdd() { 539 return versioningAfterAdd; 540 } 541 542}