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.interfaces.MimetypeRegistry; 063import org.nuxeo.ecm.platform.types.TypeManager; 064import org.nuxeo.runtime.api.Framework; 065import org.nuxeo.runtime.model.ComponentName; 066import org.nuxeo.runtime.model.DefaultComponent; 067import org.nuxeo.runtime.model.Extension; 068 069/** 070 * FileManager registry service. 071 * <p> 072 * This is the component to request to perform transformations. See API. 073 * 074 * @author <a href="mailto:andreas.kalogeropoulos@nuxeo.com">Andreas Kalogeropoulos</a> 075 */ 076public class FileManagerService extends DefaultComponent implements FileManager { 077 078 public static final ComponentName NAME = new ComponentName( 079 "org.nuxeo.ecm.platform.filemanager.service.FileManagerService"); 080 081 public static final String DEFAULT_FOLDER_TYPE_NAME = "Folder"; 082 083 // TODO: OG: we should use an overridable query model instead of hardcoding 084 // the NXQL query 085 public static final String QUERY = "SELECT * FROM Document WHERE file:content/digest = '%s'"; 086 087 public static final int MAX = 15; 088 089 private static final Log log = LogFactory.getLog(FileManagerService.class); 090 091 private final Map<String, FileImporter> fileImporters; 092 093 private final List<FolderImporter> folderImporters; 094 095 private final List<CreationContainerListProvider> creationContainerListProviders; 096 097 private List<String> fieldsXPath = new ArrayList<String>(); 098 099 private MimetypeRegistry mimeService; 100 101 private boolean unicityEnabled = false; 102 103 private String digestAlgorithm = "sha-256"; 104 105 private boolean computeDigest = false; 106 107 public static final VersioningOption DEF_VERSIONING_OPTION = VersioningOption.MINOR; 108 109 public static final boolean DEF_VERSIONING_AFTER_ADD = false; 110 111 /** 112 * @since 5.7 113 */ 114 private VersioningOption defaultVersioningOption = DEF_VERSIONING_OPTION; 115 116 /** 117 * @since 5.7 118 */ 119 private boolean versioningAfterAdd = DEF_VERSIONING_AFTER_ADD; 120 121 private TypeManager typeService; 122 123 public FileManagerService() { 124 fileImporters = new HashMap<String, FileImporter>(); 125 folderImporters = new LinkedList<FolderImporter>(); 126 creationContainerListProviders = new LinkedList<CreationContainerListProvider>(); 127 } 128 129 private MimetypeRegistry getMimeService() { 130 if (mimeService == null) { 131 mimeService = Framework.getService(MimetypeRegistry.class); 132 } 133 return mimeService; 134 } 135 136 private TypeManager getTypeService() { 137 if (typeService == null) { 138 typeService = Framework.getService(TypeManager.class); 139 } 140 return typeService; 141 } 142 143 private Blob checkMimeType(Blob blob, String fullname) { 144 String filename = FileManagerUtils.fetchFileName(fullname); 145 blob = getMimeService().updateMimetype(blob, filename, true); 146 return blob; 147 } 148 149 public DocumentModel createFolder(CoreSession documentManager, String fullname, String path) 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 return defaultCreateFolder(documentManager, fullname, path, DEFAULT_FOLDER_TYPE_NAME, true); 162 } 163 164 public DocumentModel defaultCreateFolder(CoreSession documentManager, String fullname, String path, 165 String containerTypeName, boolean checkAllowedSubTypes) { 166 167 // Fetching filename 168 String title = FileManagerUtils.fetchFileName(fullname); 169 170 // Looking if an existing Folder with the same filename exists. 171 DocumentModel docModel = FileManagerUtils.getExistingDocByTitle(documentManager, path, title); 172 173 if (docModel == null) { 174 // check permissions 175 PathRef containerRef = new PathRef(path); 176 if (!documentManager.hasPermission(containerRef, SecurityConstants.READ_PROPERTIES) 177 || !documentManager.hasPermission(containerRef, SecurityConstants.ADD_CHILDREN)) { 178 throw new DocumentSecurityException("Not enough rights to create folder"); 179 } 180 181 // check allowed sub types 182 DocumentModel container = documentManager.getDocument(containerRef); 183 if (checkAllowedSubTypes 184 && !getTypeService().isAllowedSubType(containerTypeName, container.getType(), container)) { 185 // cannot create document file here 186 // TODO: we should better raise a dedicated exception to be 187 // catched by the FileManageActionsBean instead of returning 188 // null 189 return null; 190 } 191 192 PathSegmentService pss = Framework.getService(PathSegmentService.class); 193 docModel = documentManager.createDocumentModel(containerTypeName); 194 docModel.setProperty("dublincore", "title", title); 195 196 // writing changes 197 docModel.setPathInfo(path, pss.generatePathSegment(docModel)); 198 docModel = documentManager.createDocument(docModel); 199 documentManager.save(); 200 201 log.debug("Created container: " + docModel.getName() + " with type " + containerTypeName); 202 } 203 return docModel; 204 } 205 206 public DocumentModel createDocumentFromBlob(CoreSession documentManager, Blob input, String path, boolean overwrite, 207 String fullName) throws IOException { 208 return createDocumentFromBlob(documentManager, input, path, overwrite, fullName, false); 209 } 210 211 public DocumentModel createDocumentFromBlob(CoreSession documentManager, Blob input, String path, boolean overwrite, 212 String fullName, boolean noMimeTypeCheck) throws IOException { 213 214 // check mime type to be able to select the best importer plugin 215 if (!noMimeTypeCheck) { 216 input = checkMimeType(input, fullName); 217 } 218 219 List<FileImporter> importers = new ArrayList<FileImporter>(fileImporters.values()); 220 Collections.sort(importers); 221 String normalizedMimeType = getMimeService().getMimetypeEntryByMimeType(input.getMimeType()).getNormalized(); 222 for (FileImporter importer : importers) { 223 if (importer.isEnabled() 224 && (importer.matches(normalizedMimeType) || importer.matches(input.getMimeType()))) { 225 DocumentModel doc = importer.create(documentManager, input, path, overwrite, fullName, 226 getTypeService()); 227 if (doc != null) { 228 return doc; 229 } 230 } 231 } 232 return null; 233 } 234 235 public DocumentModel updateDocumentFromBlob(CoreSession documentManager, Blob input, String path, String fullName) { 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( 378 "Unable to register file importer " + name + ", className is null or plugin is not yet registered"); 379 } 380 } 381 382 private FileImporter mergeFileImporters(FileImporter oldPlugin, FileImporter newPlugin, 383 FileImporterDescriptor desc) { 384 List<String> filters = desc.getFilters(); 385 if (filters != null && !filters.isEmpty()) { 386 List<String> oldFilters = oldPlugin.getFilters(); 387 oldFilters.addAll(filters); 388 newPlugin.setFilters(oldFilters); 389 } 390 newPlugin.setName(desc.getName()); 391 String docType = desc.getDocType(); 392 if (docType != null) { 393 newPlugin.setDocType(docType); 394 } 395 newPlugin.setFileManagerService(this); 396 newPlugin.setEnabled(desc.isEnabled()); 397 Integer order = desc.getOrder(); 398 if (order != null) { 399 newPlugin.setOrder(desc.getOrder()); 400 } 401 return newPlugin; 402 } 403 404 private FileImporter fillImporterWithDescriptor(FileImporter fileImporter, FileImporterDescriptor desc) { 405 List<String> filters = desc.getFilters(); 406 if (filters != null && !filters.isEmpty()) { 407 fileImporter.setFilters(filters); 408 } 409 fileImporter.setName(desc.getName()); 410 fileImporter.setDocType(desc.getDocType()); 411 fileImporter.setFileManagerService(this); 412 fileImporter.setEnabled(desc.isEnabled()); 413 fileImporter.setOrder(desc.getOrder()); 414 return fileImporter; 415 } 416 417 private void unregisterFileImporter(FileImporterDescriptor pluginExtension) { 418 String name = pluginExtension.getName(); 419 fileImporters.remove(name); 420 log.info("unregistered file importer: " + name); 421 } 422 423 private void registerFolderImporter(FolderImporterDescriptor folderImporterDescriptor, Extension extension) { 424 425 String name = folderImporterDescriptor.getName(); 426 String className = folderImporterDescriptor.getClassName(); 427 428 FolderImporter folderImporter; 429 try { 430 folderImporter = (FolderImporter) extension.getContext().loadClass(className).newInstance(); 431 } catch (ReflectiveOperationException e) { 432 throw new RuntimeException(e); 433 } 434 folderImporter.setName(name); 435 folderImporter.setFileManagerService(this); 436 folderImporters.add(folderImporter); 437 log.info("registered folder importer: " + name); 438 } 439 440 private void unregisterFolderImporter(FolderImporterDescriptor folderImporterDescriptor) { 441 String name = folderImporterDescriptor.getName(); 442 FolderImporter folderImporterToRemove = null; 443 for (FolderImporter folderImporter : folderImporters) { 444 if (name.equals(folderImporter.getName())) { 445 folderImporterToRemove = folderImporter; 446 } 447 } 448 if (folderImporterToRemove != null) { 449 folderImporters.remove(folderImporterToRemove); 450 } 451 log.info("unregistered folder importer: " + name); 452 } 453 454 private void registerCreationContainerListProvider(CreationContainerListProviderDescriptor ccListProviderDescriptor, 455 Extension extension) { 456 457 String name = ccListProviderDescriptor.getName(); 458 String[] docTypes = ccListProviderDescriptor.getDocTypes(); 459 String className = ccListProviderDescriptor.getClassName(); 460 461 CreationContainerListProvider provider; 462 try { 463 provider = (CreationContainerListProvider) extension.getContext().loadClass(className).newInstance(); 464 } catch (ReflectiveOperationException e) { 465 throw new RuntimeException(e); 466 } 467 provider.setName(name); 468 provider.setDocTypes(docTypes); 469 if (creationContainerListProviders.contains(provider)) { 470 // equality and containment tests are based on unique names 471 creationContainerListProviders.remove(provider); 472 } 473 // add the new provider at the beginning of the list 474 creationContainerListProviders.add(0, provider); 475 log.info("registered creationContaineterList provider: " + name); 476 } 477 478 private void unregisterCreationContainerListProvider( 479 CreationContainerListProviderDescriptor ccListProviderDescriptor) { 480 String name = ccListProviderDescriptor.getName(); 481 CreationContainerListProvider providerToRemove = null; 482 for (CreationContainerListProvider provider : creationContainerListProviders) { 483 if (name.equals(provider.getName())) { 484 providerToRemove = provider; 485 break; 486 } 487 } 488 if (providerToRemove != null) { 489 creationContainerListProviders.remove(providerToRemove); 490 } 491 log.info("unregistered creationContaineterList provider: " + name); 492 } 493 494 public List<DocumentLocation> findExistingDocumentWithFile(CoreSession documentManager, String path, String digest, 495 Principal principal) { 496 String nxql = String.format(QUERY, digest); 497 DocumentModelList documentModelList = documentManager.query(nxql, MAX); 498 List<DocumentLocation> docLocationList = new ArrayList<DocumentLocation>(documentModelList.size()); 499 for (DocumentModel documentModel : documentModelList) { 500 docLocationList.add(new DocumentLocationImpl(documentModel)); 501 } 502 return docLocationList; 503 } 504 505 public boolean isUnicityEnabled() { 506 return unicityEnabled; 507 } 508 509 public boolean isDigestComputingEnabled() { 510 return computeDigest; 511 } 512 513 public List<String> getFields() { 514 return fieldsXPath; 515 } 516 517 public DocumentModelList getCreationContainers(Principal principal, String docType) { 518 DocumentModelList containers = new DocumentModelListImpl(); 519 RepositoryManager repositoryManager = Framework.getLocalService(RepositoryManager.class); 520 for (String repositoryName : repositoryManager.getRepositoryNames()) { 521 try (CoreSession session = CoreInstance.openCoreSession(repositoryName, principal)) { 522 containers.addAll(getCreationContainers(session, docType)); 523 } 524 } 525 return containers; 526 } 527 528 public DocumentModelList getCreationContainers(CoreSession documentManager, String docType) { 529 for (CreationContainerListProvider provider : creationContainerListProviders) { 530 if (provider.accept(docType)) { 531 return provider.getCreationContainerList(documentManager, docType); 532 } 533 } 534 return new DocumentModelListImpl(); 535 } 536 537 public String getDigestAlgorithm() { 538 return digestAlgorithm; 539 } 540 541 @Override 542 public VersioningOption getVersioningOption() { 543 return defaultVersioningOption; 544 } 545 546 @Override 547 public boolean doVersioningAfterAdd() { 548 return versioningAfterAdd; 549 } 550 551}