001/* 002 * (C) Copyright 2006-2018 Nuxeo (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 */ 019package org.nuxeo.ecm.platform.filemanager.service; 020 021import java.io.IOException; 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.LinkedList; 026import java.util.List; 027import java.util.Locale; 028import java.util.Map; 029 030import org.apache.commons.lang3.StringUtils; 031import org.apache.logging.log4j.LogManager; 032import org.apache.logging.log4j.Logger; 033import org.nuxeo.ecm.core.api.Blob; 034import org.nuxeo.ecm.core.api.CloseableCoreSession; 035import org.nuxeo.ecm.core.api.CoreInstance; 036import org.nuxeo.ecm.core.api.CoreSession; 037import org.nuxeo.ecm.core.api.DocumentLocation; 038import org.nuxeo.ecm.core.api.DocumentModel; 039import org.nuxeo.ecm.core.api.DocumentModelList; 040import org.nuxeo.ecm.core.api.DocumentSecurityException; 041import org.nuxeo.ecm.core.api.NuxeoException; 042import org.nuxeo.ecm.core.api.NuxeoPrincipal; 043import org.nuxeo.ecm.core.api.PathRef; 044import org.nuxeo.ecm.core.api.VersioningOption; 045import org.nuxeo.ecm.core.api.impl.DocumentLocationImpl; 046import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; 047import org.nuxeo.ecm.core.api.pathsegment.PathSegmentService; 048import org.nuxeo.ecm.core.api.repository.RepositoryManager; 049import org.nuxeo.ecm.core.api.security.SecurityConstants; 050import org.nuxeo.ecm.platform.filemanager.api.FileImporterContext; 051import org.nuxeo.ecm.platform.filemanager.api.FileManager; 052import org.nuxeo.ecm.platform.filemanager.service.extension.CreationContainerListProvider; 053import org.nuxeo.ecm.platform.filemanager.service.extension.CreationContainerListProviderDescriptor; 054import org.nuxeo.ecm.platform.filemanager.service.extension.FileImporter; 055import org.nuxeo.ecm.platform.filemanager.service.extension.FileImporterDescriptor; 056import org.nuxeo.ecm.platform.filemanager.service.extension.FolderImporter; 057import org.nuxeo.ecm.platform.filemanager.service.extension.FolderImporterDescriptor; 058import org.nuxeo.ecm.platform.filemanager.service.extension.UnicityExtension; 059import org.nuxeo.ecm.platform.filemanager.service.extension.VersioningDescriptor; 060import org.nuxeo.ecm.platform.filemanager.utils.FileManagerUtils; 061import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry; 062import org.nuxeo.ecm.platform.types.TypeManager; 063import org.nuxeo.runtime.api.Framework; 064import org.nuxeo.runtime.logging.DeprecationLogger; 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:[email protected]">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 Logger log = LogManager.getLogger(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<>(); 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 * @deprecated since 9.1 automatic versioning is now handled at versioning service level, remove versioning 114 * behaviors from importers 115 */ 116 @Deprecated 117 private VersioningOption defaultVersioningOption = DEF_VERSIONING_OPTION; 118 119 /** 120 * @since 5.7 121 * @deprecated since 9.1 automatic versioning is now handled at versioning service level, remove versioning 122 * behaviors from importers 123 */ 124 @Deprecated 125 private boolean versioningAfterAdd = DEF_VERSIONING_AFTER_ADD; 126 127 private TypeManager typeService; 128 129 public FileManagerService() { 130 fileImporters = new HashMap<>(); 131 folderImporters = new LinkedList<>(); 132 creationContainerListProviders = new LinkedList<>(); 133 } 134 135 private MimetypeRegistry getMimeService() { 136 if (mimeService == null) { 137 mimeService = Framework.getService(MimetypeRegistry.class); 138 } 139 return mimeService; 140 } 141 142 private TypeManager getTypeService() { 143 if (typeService == null) { 144 typeService = Framework.getService(TypeManager.class); 145 } 146 return typeService; 147 } 148 149 private Blob checkMimeType(Blob blob, String fullname) { 150 String filename = FileManagerUtils.fetchFileName(fullname); 151 blob = getMimeService().updateMimetype(blob, filename, true); 152 return blob; 153 } 154 155 @Override 156 public DocumentModel createFolder(CoreSession documentManager, String fullname, String path, boolean overwrite) 157 throws IOException { 158 159 if (folderImporters.isEmpty()) { 160 return defaultCreateFolder(documentManager, fullname, path, overwrite); 161 } else { 162 // use the last registered folder importer 163 FolderImporter folderImporter = folderImporters.get(folderImporters.size() - 1); 164 return folderImporter.create(documentManager, fullname, path, overwrite, getTypeService()); 165 } 166 } 167 168 /** 169 * @deprecated since 9.1, use {@link #defaultCreateFolder(CoreSession, String, String, boolean)} instead 170 */ 171 @Deprecated 172 public DocumentModel defaultCreateFolder(CoreSession documentManager, String fullname, String path) { 173 return defaultCreateFolder(documentManager, fullname, path, true); 174 } 175 176 /** 177 * @since 9.1 178 */ 179 public DocumentModel defaultCreateFolder(CoreSession documentManager, String fullname, String path, 180 boolean overwrite) { 181 return defaultCreateFolder(documentManager, fullname, path, DEFAULT_FOLDER_TYPE_NAME, true, overwrite); 182 } 183 184 /** 185 * @deprecated since 9.1, use {@link #defaultCreateFolder(CoreSession, String, String, String, boolean, boolean)} 186 * instead 187 */ 188 @Deprecated 189 public DocumentModel defaultCreateFolder(CoreSession documentManager, String fullname, String path, 190 String containerTypeName, boolean checkAllowedSubTypes) { 191 return defaultCreateFolder(documentManager, fullname, path, containerTypeName, checkAllowedSubTypes, true); 192 } 193 194 /** 195 * @since 9.1 196 */ 197 public DocumentModel defaultCreateFolder(CoreSession documentManager, String fullname, String path, 198 String containerTypeName, boolean checkAllowedSubTypes, boolean overwrite) { 199 200 // Fetching filename 201 String title = FileManagerUtils.fetchFileName(fullname); 202 203 if (overwrite) { 204 // Looking if an existing Folder with the same filename exists. 205 DocumentModel docModel = FileManagerUtils.getExistingDocByTitle(documentManager, path, title); 206 if (docModel != null) { 207 return docModel; 208 } 209 } 210 211 // check permissions 212 PathRef containerRef = new PathRef(path); 213 if (!documentManager.hasPermission(containerRef, SecurityConstants.READ_PROPERTIES) 214 || !documentManager.hasPermission(containerRef, SecurityConstants.ADD_CHILDREN)) { 215 throw new DocumentSecurityException("Not enough rights to create folder"); 216 } 217 218 // check allowed sub types 219 DocumentModel container = documentManager.getDocument(containerRef); 220 if (checkAllowedSubTypes 221 && !getTypeService().isAllowedSubType(containerTypeName, container.getType(), container)) { 222 // cannot create document file here 223 // TODO: we should better raise a dedicated exception to be 224 // catched by the FileManageActionsBean instead of returning 225 // null 226 return null; 227 } 228 229 PathSegmentService pss = Framework.getService(PathSegmentService.class); 230 DocumentModel docModel = documentManager.createDocumentModel(containerTypeName); 231 docModel.setProperty("dublincore", "title", title); 232 233 // writing changes 234 docModel.setPathInfo(path, pss.generatePathSegment(docModel)); 235 docModel = documentManager.createDocument(docModel); 236 documentManager.save(); 237 238 log.debug("Created container: {} with type {}", docModel::getName, () -> containerTypeName); 239 return docModel; 240 } 241 242 @Override 243 public DocumentModel createDocumentFromBlob(CoreSession documentManager, Blob input, String path, boolean overwrite, 244 String fullName) throws IOException { 245 return createDocumentFromBlob(documentManager, input, path, overwrite, fullName, false); 246 } 247 248 @Override 249 public DocumentModel createDocumentFromBlob(CoreSession documentManager, Blob input, String path, boolean overwrite, 250 String fullName, boolean noMimeTypeCheck) throws IOException { 251 FileImporterContext context = FileImporterContext.builder(documentManager, input, path) 252 .overwrite(overwrite) 253 .fileName(fullName) 254 .mimeTypeCheck(!noMimeTypeCheck) 255 .build(); 256 return createOrUpdateDocument(context); 257 } 258 259 @Override 260 public DocumentModel createOrUpdateDocument(FileImporterContext context) throws IOException { 261 Blob blob = context.getBlob(); 262 263 // check mime type to be able to select the best importer plugin 264 if (context.isMimeTypeCheck()) { 265 String fileName = StringUtils.defaultIfBlank(context.getFileName(), blob.getFilename()); 266 blob = checkMimeType(blob, fileName); 267 } 268 269 List<FileImporter> importers = new ArrayList<>(fileImporters.values()); 270 Collections.sort(importers); 271 String mimeType = blob.getMimeType(); 272 String normalizedMimeType = getMimeService().getMimetypeEntryByMimeType(mimeType).getNormalized(); 273 for (FileImporter importer : importers) { 274 if (isImporterAvailable(importer, normalizedMimeType, mimeType, context.isExcludeOneToMany())) { 275 DocumentModel doc = importer.createOrUpdate(context); 276 if (doc != null) { 277 return doc; 278 } 279 } 280 } 281 return null; 282 } 283 284 protected boolean isImporterAvailable(FileImporter importer, String normalizedMimeType, String mimeType, 285 boolean excludeOneToMany) { 286 return importer.isEnabled() && !(importer.isOneToMany() && excludeOneToMany) 287 && (importer.matches(normalizedMimeType) || importer.matches(mimeType)); 288 } 289 290 @Override 291 public DocumentModel updateDocumentFromBlob(CoreSession documentManager, Blob input, String path, String fullName) { 292 String filename = FileManagerUtils.fetchFileName(fullName); 293 DocumentModel doc = FileManagerUtils.getExistingDocByFileName(documentManager, path, filename); 294 if (doc != null) { 295 doc.setProperty("file", "content", input); 296 297 documentManager.saveDocument(doc); 298 documentManager.save(); 299 300 log.debug("Updated the document: {}", doc::getName); 301 } 302 return doc; 303 } 304 305 public FileImporter getPluginByName(String name) { 306 return fileImporters.get(name); 307 } 308 309 @Override 310 public void registerExtension(Extension extension) { 311 if (extension.getExtensionPoint().equals("plugins")) { 312 Object[] contribs = extension.getContributions(); 313 for (Object contrib : contribs) { 314 if (contrib instanceof FileImporterDescriptor) { 315 registerFileImporter((FileImporterDescriptor) contrib, extension); 316 } else if (contrib instanceof FolderImporterDescriptor) { 317 registerFolderImporter((FolderImporterDescriptor) contrib, extension); 318 } else if (contrib instanceof CreationContainerListProviderDescriptor) { 319 registerCreationContainerListProvider((CreationContainerListProviderDescriptor) contrib, extension); 320 } 321 } 322 } else if (extension.getExtensionPoint().equals("unicity")) { 323 Object[] contribs = extension.getContributions(); 324 for (Object contrib : contribs) { 325 if (contrib instanceof UnicityExtension) { 326 registerUnicityOptions((UnicityExtension) contrib); 327 } 328 } 329 } else if (extension.getExtensionPoint().equals("versioning")) { 330 String message = "Extension point 'versioning' has been deprecated and corresponding behavior removed from " 331 + "Nuxeo Platform. Please use versioning policy instead."; 332 DeprecationLogger.log(message, "9.1"); 333 Framework.getRuntime().getMessageHandler().addWarning(message); 334 Object[] contribs = extension.getContributions(); 335 for (Object contrib : contribs) { 336 if (contrib instanceof VersioningDescriptor) { 337 VersioningDescriptor descr = (VersioningDescriptor) contrib; 338 String defver = descr.defaultVersioningOption; 339 if (!StringUtils.isBlank(defver)) { 340 try { 341 defaultVersioningOption = VersioningOption.valueOf(defver.toUpperCase(Locale.ENGLISH)); 342 } catch (IllegalArgumentException e) { 343 log.warn("Illegal versioning option: {}, using {} instead", defver, DEF_VERSIONING_OPTION); 344 defaultVersioningOption = DEF_VERSIONING_OPTION; 345 } 346 } 347 Boolean veradd = descr.versionAfterAdd; 348 if (veradd != null) { 349 versioningAfterAdd = veradd.booleanValue(); 350 } 351 } 352 } 353 } else { 354 log.warn("Unknown contribution {}: ignored", extension::getExtensionPoint); 355 } 356 } 357 358 @Override 359 public void unregisterExtension(Extension extension) { 360 if (extension.getExtensionPoint().equals("plugins")) { 361 Object[] contribs = extension.getContributions(); 362 363 for (Object contrib : contribs) { 364 if (contrib instanceof FileImporterDescriptor) { 365 unregisterFileImporter((FileImporterDescriptor) contrib); 366 } else if (contrib instanceof FolderImporterDescriptor) { 367 unregisterFolderImporter((FolderImporterDescriptor) contrib); 368 } else if (contrib instanceof CreationContainerListProviderDescriptor) { 369 unregisterCreationContainerListProvider((CreationContainerListProviderDescriptor) contrib); 370 } 371 } 372 } else if (extension.getExtensionPoint().equals("unicity")) { 373 // nothing to do 374 375 } else if (extension.getExtensionPoint().equals("versioning")) { 376 // set to default value 377 defaultVersioningOption = DEF_VERSIONING_OPTION; 378 versioningAfterAdd = DEF_VERSIONING_AFTER_ADD; 379 } else { 380 log.warn("Unknown contribution {}: ignored", extension::getExtensionPoint); 381 } 382 } 383 384 private void registerUnicityOptions(UnicityExtension unicityExtension) { 385 if (unicityExtension.getAlgo() != null) { 386 digestAlgorithm = unicityExtension.getAlgo(); 387 } 388 if (unicityExtension.getEnabled() != null) { 389 unicityEnabled = unicityExtension.getEnabled().booleanValue(); 390 } 391 if (unicityExtension.getFields() != null) { 392 fieldsXPath = unicityExtension.getFields(); 393 } else { 394 fieldsXPath.add("file:content"); 395 } 396 if (unicityExtension.getComputeDigest() != null) { 397 computeDigest = unicityExtension.getComputeDigest().booleanValue(); 398 } 399 } 400 401 private void registerFileImporter(FileImporterDescriptor pluginExtension, Extension extension) { 402 String name = pluginExtension.getName(); 403 if (name == null) { 404 log.error("Cannot register file importer without a name"); 405 return; 406 } 407 408 String className = pluginExtension.getClassName(); 409 if (fileImporters.containsKey(name)) { 410 log.info("Overriding file importer plugin {}", name); 411 FileImporter oldPlugin = fileImporters.get(name); 412 FileImporter newPlugin; 413 try { 414 newPlugin = className != null ? (FileImporter) extension.getContext().loadClass(className).newInstance() 415 : oldPlugin; 416 } catch (ReflectiveOperationException e) { 417 throw new NuxeoException(e); 418 } 419 if (pluginExtension.isMerge()) { 420 mergeFileImporters(oldPlugin, newPlugin, pluginExtension); 421 } else { 422 fillImporterWithDescriptor(newPlugin, pluginExtension); 423 } 424 fileImporters.put(name, newPlugin); 425 log.info("Registered file importer {}", name); 426 } else if (className != null) { 427 FileImporter plugin; 428 try { 429 plugin = (FileImporter) extension.getContext().loadClass(className).newInstance(); 430 } catch (ReflectiveOperationException e) { 431 throw new NuxeoException(e); 432 } 433 fillImporterWithDescriptor(plugin, pluginExtension); 434 fileImporters.put(name, plugin); 435 log.info("Registered file importer {}", name); 436 } else { 437 log.info("Unable to register file importer {}, className is null or plugin is not yet registered", name); 438 } 439 } 440 441 private void mergeFileImporters(FileImporter oldPlugin, FileImporter newPlugin, FileImporterDescriptor desc) { 442 List<String> filters = desc.getFilters(); 443 if (filters != null && !filters.isEmpty()) { 444 List<String> oldFilters = oldPlugin.getFilters(); 445 oldFilters.addAll(filters); 446 newPlugin.setFilters(oldFilters); 447 } 448 newPlugin.setName(desc.getName()); 449 String docType = desc.getDocType(); 450 if (docType != null) { 451 newPlugin.setDocType(docType); 452 } 453 newPlugin.setFileManagerService(this); 454 newPlugin.setEnabled(desc.isEnabled()); 455 Integer order = desc.getOrder(); 456 if (order != null) { 457 newPlugin.setOrder(desc.getOrder()); 458 } 459 } 460 461 private void fillImporterWithDescriptor(FileImporter fileImporter, FileImporterDescriptor desc) { 462 List<String> filters = desc.getFilters(); 463 if (filters != null && !filters.isEmpty()) { 464 fileImporter.setFilters(filters); 465 } 466 fileImporter.setName(desc.getName()); 467 fileImporter.setDocType(desc.getDocType()); 468 fileImporter.setFileManagerService(this); 469 fileImporter.setEnabled(desc.isEnabled()); 470 fileImporter.setOrder(desc.getOrder()); 471 } 472 473 private void unregisterFileImporter(FileImporterDescriptor pluginExtension) { 474 String name = pluginExtension.getName(); 475 fileImporters.remove(name); 476 log.info("unregistered file importer: {}", name); 477 } 478 479 private void registerFolderImporter(FolderImporterDescriptor folderImporterDescriptor, Extension extension) { 480 481 String name = folderImporterDescriptor.getName(); 482 String className = folderImporterDescriptor.getClassName(); 483 484 FolderImporter folderImporter; 485 try { 486 folderImporter = (FolderImporter) extension.getContext().loadClass(className).newInstance(); 487 } catch (ReflectiveOperationException e) { 488 throw new NuxeoException(e); 489 } 490 folderImporter.setName(name); 491 folderImporter.setFileManagerService(this); 492 folderImporters.add(folderImporter); 493 log.info("registered folder importer: {}", name); 494 } 495 496 private void unregisterFolderImporter(FolderImporterDescriptor folderImporterDescriptor) { 497 String name = folderImporterDescriptor.getName(); 498 FolderImporter folderImporterToRemove = null; 499 for (FolderImporter folderImporter : folderImporters) { 500 if (name.equals(folderImporter.getName())) { 501 folderImporterToRemove = folderImporter; 502 } 503 } 504 if (folderImporterToRemove != null) { 505 folderImporters.remove(folderImporterToRemove); 506 } 507 log.info("unregistered folder importer: {}", name); 508 } 509 510 private void registerCreationContainerListProvider(CreationContainerListProviderDescriptor ccListProviderDescriptor, 511 Extension extension) { 512 513 String name = ccListProviderDescriptor.getName(); 514 String[] docTypes = ccListProviderDescriptor.getDocTypes(); 515 String className = ccListProviderDescriptor.getClassName(); 516 517 CreationContainerListProvider provider; 518 try { 519 provider = (CreationContainerListProvider) extension.getContext().loadClass(className).newInstance(); 520 } catch (ReflectiveOperationException e) { 521 throw new NuxeoException(e); 522 } 523 provider.setName(name); 524 provider.setDocTypes(docTypes); 525 if (creationContainerListProviders.contains(provider)) { 526 // equality and containment tests are based on unique names 527 creationContainerListProviders.remove(provider); 528 } 529 // add the new provider at the beginning of the list 530 creationContainerListProviders.add(0, provider); 531 log.info("registered creationContaineterList provider: {}", name); 532 } 533 534 private void unregisterCreationContainerListProvider( 535 CreationContainerListProviderDescriptor ccListProviderDescriptor) { 536 String name = ccListProviderDescriptor.getName(); 537 CreationContainerListProvider providerToRemove = null; 538 for (CreationContainerListProvider provider : creationContainerListProviders) { 539 if (name.equals(provider.getName())) { 540 providerToRemove = provider; 541 break; 542 } 543 } 544 if (providerToRemove != null) { 545 creationContainerListProviders.remove(providerToRemove); 546 } 547 log.info("unregistered creationContaineterList provider: {}", name); 548 } 549 550 @Override 551 public List<DocumentLocation> findExistingDocumentWithFile(CoreSession documentManager, String path, String digest, 552 NuxeoPrincipal principal) { 553 String nxql = String.format(QUERY, digest); 554 DocumentModelList documentModelList = documentManager.query(nxql, MAX); 555 List<DocumentLocation> docLocationList = new ArrayList<>(documentModelList.size()); 556 for (DocumentModel documentModel : documentModelList) { 557 docLocationList.add(new DocumentLocationImpl(documentModel)); 558 } 559 return docLocationList; 560 } 561 562 @Override 563 public boolean isUnicityEnabled() { 564 return unicityEnabled; 565 } 566 567 @Override 568 public boolean isDigestComputingEnabled() { 569 return computeDigest; 570 } 571 572 @Override 573 public List<String> getFields() { 574 return fieldsXPath; 575 } 576 577 @Override 578 public DocumentModelList getCreationContainers(NuxeoPrincipal principal, String docType) { 579 DocumentModelList containers = new DocumentModelListImpl(); 580 RepositoryManager repositoryManager = Framework.getService(RepositoryManager.class); 581 for (String repositoryName : repositoryManager.getRepositoryNames()) { 582 try (CloseableCoreSession session = CoreInstance.openCoreSession(repositoryName, principal)) { 583 DocumentModelList docs = getCreationContainers(session, docType); 584 docs.forEach(doc -> doc.detach(true)); 585 containers.addAll(docs); 586 } 587 } 588 return containers; 589 } 590 591 @Override 592 public DocumentModelList getCreationContainers(CoreSession documentManager, String docType) { 593 for (CreationContainerListProvider provider : creationContainerListProviders) { 594 if (provider.accept(docType)) { 595 return provider.getCreationContainerList(documentManager, docType); 596 } 597 } 598 return new DocumentModelListImpl(); 599 } 600 601 @Override 602 public String getDigestAlgorithm() { 603 return digestAlgorithm; 604 } 605 606 /** 607 * @deprecated since 9.1 automatic versioning is now handled at versioning service level, remove versioning 608 * behaviors from importers 609 */ 610 @Override 611 @Deprecated 612 public VersioningOption getVersioningOption() { 613 return defaultVersioningOption; 614 } 615 616 /** 617 * @deprecated since 9.1 automatic versioning is now handled at versioning service level, remove versioning 618 * behaviors from importers 619 */ 620 @Override 621 @Deprecated 622 public boolean doVersioningAfterAdd() { 623 return versioningAfterAdd; 624 } 625 626}