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