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: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 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}