org.kitodo.export.ExportDms.java Source code

Java tutorial

Introduction

Here is the source code for org.kitodo.export.ExportDms.java

Source

/*
 * (c) Kitodo. Key to digital objects e. V. <contact@kitodo.org>
 *
 * This file is part of the Kitodo project.
 *
 * It is licensed under GNU General Public License version 3 or later.
 *
 * For the full copyright and license information, please read the
 * GPL3-License.txt file that was distributed with this source code.
 */

package org.kitodo.export;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.stream.Collectors;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.kitodo.config.ConfigCore;
import org.kitodo.config.enums.ParameterCore;
import org.kitodo.data.database.beans.Folder;
import org.kitodo.data.database.beans.Process;
import org.kitodo.data.database.beans.Project;
import org.kitodo.data.database.beans.User;
import org.kitodo.data.database.enums.MetadataFormat;
import org.kitodo.production.helper.Helper;
import org.kitodo.production.helper.VariableReplacer;
import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyDocStructHelperInterface;
import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyMetadataHelper;
import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyMetsModsDigitalDocumentHelper;
import org.kitodo.production.helper.tasks.EmptyTask;
import org.kitodo.production.helper.tasks.ExportDmsTask;
import org.kitodo.production.helper.tasks.TaskManager;
import org.kitodo.production.helper.tasks.TaskSitter;
import org.kitodo.production.metadata.copier.CopierData;
import org.kitodo.production.metadata.copier.DataCopier;
import org.kitodo.production.model.Subfolder;
import org.kitodo.production.services.ServiceManager;
import org.kitodo.production.services.file.FileService;

public class ExportDms extends ExportMets {
    private static final Logger logger = LogManager.getLogger(ExportDms.class);
    private String atsPpnBand;
    private boolean exportWithImages = true;
    private final FileService fileService = ServiceManager.getFileService();
    private static final String EXPORT_DIR_DELETE = "errorDirectoryDeleting";
    private static final String ERROR_EXPORT = "errorExport";

    /**
     * The field exportDmsTask holds an optional task instance. Its progress and
     * its errors will be passed to the task manager screen (if available) for
     * visualisation.
     */
    private EmptyTask exportDmsTask = null;

    public ExportDms() {
    }

    public ExportDms(boolean exportImages) {
        this.exportWithImages = exportImages;
    }

    /**
     * DMS-Export an eine gewnschte Stelle.
     *
     * @param process
     *            object
     * @param destination
     *            String
     */
    @Override
    public boolean startExport(Process process, URI destination) {
        if (process.getProject().isUseDmsImport()
                && ConfigCore.getBooleanParameterOrDefaultValue(ParameterCore.ASYNCHRONOUS_AUTOMATIC_EXPORT)) {
            TaskManager.addTask(new ExportDmsTask(this, process, destination));
            Helper.setMessage(TaskSitter.isAutoRunningThreads() ? "DMSExportByThread" : "DMSExportThreadCreated",
                    process.getTitle());
            return true;
        } else {
            return startExport(process, destination, (ExportDmsTask) null);
        }
    }

    /**
     * The function startExport() performs a DMS export to a desired place. In
     * addition, it accepts an optional ExportDmsTask object. If that is passed
     * in, the progress in it will be updated during processing and occurring
     * errors will be passed to it to be visible in the task manager screen.
     *
     * @param process
     *            process to export
     * @param destination
     *            work directory of the user who triggered the export
     * @param exportDmsTask
     *            ExportDmsTask object to submit progress updates and errors
     * @return false if an error condition was caught, true otherwise
     */
    public boolean startExport(Process process, URI destination, ExportDmsTask exportDmsTask) {
        this.exportDmsTask = exportDmsTask;
        try {
            return startExport(process, destination,
                    ServiceManager.getProcessService().readMetadataFile(process).getDigitalDocument());
        } catch (IOException | RuntimeException e) {
            if (Objects.nonNull(exportDmsTask)) {
                exportDmsTask.setException(e);
                logger.error(Helper.getTranslation(ERROR_EXPORT, Collections.singletonList(process.getTitle())), e);
            } else {
                Helper.setErrorMessage(ERROR_EXPORT, new Object[] { process.getTitle() }, logger, e);
            }
            return false;
        }
    }

    /**
     * Start export.
     *
     * @param process
     *            object
     * @param destination
     *            String
     * @param newFile
     *            DigitalDocument
     * @return boolean
     */
    public boolean startExport(Process process, URI destination, LegacyMetsModsDigitalDocumentHelper newFile)
            throws IOException {

        this.myPrefs = ServiceManager.getRulesetService().getPreferences(process.getRuleset());
        this.atsPpnBand = Helper.getNormalizedTitle(process.getTitle());

        LegacyMetsModsDigitalDocumentHelper gdzfile = readDocument(process, newFile);
        if (Objects.isNull(gdzfile)) {
            return false;
        }

        boolean dataCopierResult = executeDataCopierProcess(gdzfile, process);
        if (!dataCopierResult) {
            return false;
        }

        trimAllMetadata(gdzfile.getDigitalDocument().getLogicalDocStruct());

        // validate metadata
        if (ConfigCore.getBooleanParameterOrDefaultValue(ParameterCore.USE_META_DATA_VALIDATION)
                && !ServiceManager.getMetadataValidationService().validate(gdzfile, this.myPrefs, process)) {
            return false;
        }

        return prepareAndDownloadSaveLocation(process, destination, gdzfile);
    }

    private boolean prepareAndDownloadSaveLocation(Process process, URI destinationDirectory,
            LegacyMetsModsDigitalDocumentHelper gdzfile) throws IOException {
        // TODO: why create again destinationDirectory if it is already given as
        // an
        // input??
        URI destination;
        URI userHome;
        if (process.getProject().isUseDmsImport()) {
            // TODO: I have got here value usr/local/kitodo/hotfolder
            destination = new File(process.getProject().getDmsImportImagesPath()).toURI();
            userHome = destination;

            // if necessary, create process folder
            if (process.getProject().isDmsImportCreateProcessFolder()) {
                URI userHomeProcess = fileService.createResource(userHome,
                        File.separator + Helper.getNormalizedTitle(process.getTitle()));
                destination = userHomeProcess;
                boolean createProcessFolderResult = createProcessFolder(userHomeProcess, userHome,
                        process.getProject(), process.getTitle());
                if (!createProcessFolderResult) {
                    return false;
                }
            }
        } else {
            destination = URI.create(destinationDirectory + atsPpnBand + "/");
            // if the home exists, first delete and then create again
            userHome = destination;
            if (!fileService.delete(userHome)) {
                Helper.setErrorMessage(
                        Helper.getTranslation(ERROR_EXPORT, Collections.singletonList(process.getTitle())),
                        Helper.getTranslation(EXPORT_DIR_DELETE, Collections.singletonList("Home")));
                return false;
            }
            prepareUserDirectory(destination);
        }
        if (Objects.nonNull(exportDmsTask)) {
            exportDmsTask.setProgress(1);
        }

        return exportImagesAndMetsToDestinationUri(process, gdzfile, destination, userHome);
    }

    private boolean exportImagesAndMetsToDestinationUri(Process process,
            LegacyMetsModsDigitalDocumentHelper gdzfile, URI destination, URI userHome) throws IOException {

        if (exportWithImages) {
            try {
                directoryDownload(process, destination);
            } catch (IOException | InterruptedException | RuntimeException e) {
                if (Objects.nonNull(exportDmsTask)) {
                    exportDmsTask.setException(e);
                } else {
                    Helper.setErrorMessage(ERROR_EXPORT, new Object[] { process.getTitle() }, logger, e);
                }
                return false;
            }
        }

        /*
         * export the file to the desired location, either directly into the import
         * folder or into the user's home, then start the import thread
         */
        if (process.getProject().isUseDmsImport()) {
            asyncExportWithImport(process, gdzfile, userHome);
        } else {
            exportWithoutImport(process, gdzfile, userHome);
        }
        return true;
    }

    private boolean executeDataCopierProcess(LegacyMetsModsDigitalDocumentHelper gdzfile, Process process) {
        try {
            String rules = ConfigCore.getParameter(ParameterCore.COPY_DATA_ON_EXPORT);
            if (Objects.nonNull(rules) && !executeDataCopierProcess(gdzfile, process, rules)) {
                return false;
            }
        } catch (NoSuchElementException e) {
            logger.catching(Level.TRACE, e);
            // no configuration simply means here is nothing to do
        }
        return true;
    }

    private boolean executeDataCopierProcess(LegacyMetsModsDigitalDocumentHelper gdzfile, Process process,
            String rules) {
        try {
            new DataCopier(rules).process(new CopierData(gdzfile, process));
        } catch (ConfigurationException e) {
            if (Objects.nonNull(exportDmsTask)) {
                exportDmsTask.setException(e);
            } else {
                Helper.setErrorMessage("dataCopier.syntaxError", e.getMessage(), logger, e);
                return false;
            }
        }
        return true;
    }

    private boolean createProcessFolder(URI userHomeProcess, URI userHome, Project project, String processTitle)
            throws IOException {
        String normalizedTitle = Helper.getNormalizedTitle(processTitle);

        // delete old import folder
        if (!fileService.delete(userHomeProcess)) {
            Helper.setErrorMessage(Helper.getTranslation(ERROR_EXPORT, Collections.singletonList(processTitle)),
                    Helper.getTranslation(EXPORT_DIR_DELETE, Collections.singletonList("Import")));
            return false;
        }
        // delete old success folder
        URI successFolder = URI.create(project.getDmsImportSuccessPath() + "/" + normalizedTitle);
        if (!fileService.delete(successFolder)) {
            Helper.setErrorMessage(Helper.getTranslation(ERROR_EXPORT, Collections.singletonList(processTitle)),
                    Helper.getTranslation(EXPORT_DIR_DELETE, Collections.singletonList("Success")));
            return false;
        }
        // delete old error folder
        URI errorFolder = URI.create(project.getDmsImportErrorPath() + "/" + normalizedTitle);
        if (!fileService.delete(errorFolder)) {
            Helper.setErrorMessage(Helper.getTranslation(ERROR_EXPORT, Collections.singletonList(processTitle)),
                    Helper.getTranslation(EXPORT_DIR_DELETE, Collections.singletonList("Error")));
            return false;
        }

        if (!fileService.fileExist(userHomeProcess)) {
            fileService.createDirectory(userHome, normalizedTitle);
        }

        return true;
    }

    private LegacyMetsModsDigitalDocumentHelper readDocument(Process process,
            LegacyMetsModsDigitalDocumentHelper newFile) {
        LegacyMetsModsDigitalDocumentHelper gdzfile;
        try {
            switch (MetadataFormat.findFileFormatsHelperByName(process.getProject().getFileFormatDmsExport())) {
            case METS:
                gdzfile = new LegacyMetsModsDigitalDocumentHelper(this.myPrefs.getRuleset());
                break;
            case METS_AND_RDF:
            default:
                throw new UnsupportedOperationException("Dead code pending removal");
            }

            gdzfile.setDigitalDocument(newFile);
            return gdzfile;
        } catch (RuntimeException e) {
            if (Objects.nonNull(exportDmsTask)) {
                exportDmsTask.setException(e);
                logger.error(Helper.getTranslation(ERROR_EXPORT, Collections.singletonList(process.getTitle())), e);
            } else {
                Helper.setErrorMessage(ERROR_EXPORT, new Object[] { process.getTitle() }, logger, e);
            }
            return null;
        }
    }

    private void asyncExportWithImport(Process process, LegacyMetsModsDigitalDocumentHelper gdzfile, URI userHome)
            throws IOException {
        String fileFormat = process.getProject().getFileFormatDmsExport();

        if (Objects.nonNull(exportDmsTask)) {
            exportDmsTask.setWorkDetail(atsPpnBand + ".xml");
        }
        if (MetadataFormat.findFileFormatsHelperByName(fileFormat) == MetadataFormat.METS) {
            // if METS, then write by writeMetsFile...
            writeMetsFile(process, fileService.createResource(userHome, File.separator + atsPpnBand + ".xml"),
                    gdzfile);
        } else {
            // ...if not, just write a fileformat
            gdzfile.write(userHome + File.separator + atsPpnBand + ".xml");
        }

        // if necessary, METS and RDF should be written in the export
        if (MetadataFormat.findFileFormatsHelperByName(fileFormat) == MetadataFormat.METS_AND_RDF) {
            writeMetsFile(process, fileService.createResource(userHome, File.separator + atsPpnBand + ".mets.xml"),
                    gdzfile);
        }

        Helper.setMessage(process.getTitle() + ": ", "DMS-Export started");
        if (!ConfigCore.getBooleanParameterOrDefaultValue(ParameterCore.EXPORT_WITHOUT_TIME_LIMIT)) {
            exportWithTimeLimit(process);
        }
        if (Objects.nonNull(exportDmsTask)) {
            exportDmsTask.setProgress(100);
        }
    }

    private void exportWithTimeLimit(Process process) throws IOException {
        DmsImportThread asyncThread = new DmsImportThread(process, atsPpnBand);
        asyncThread.start();
        String processTitle = process.getTitle();

        try {
            // wait 30 seconds for the thread, possibly kill
            asyncThread.join(process.getProject().getDmsImportTimeOut().longValue());
            if (asyncThread.isAlive()) {
                asyncThread.stopThread();
            }
        } catch (InterruptedException e) {
            if (Objects.nonNull(exportDmsTask)) {
                exportDmsTask.setException(e);
                logger.error(Helper.getTranslation(ERROR_EXPORT, Collections.singletonList(processTitle)));
            } else {
                Thread.currentThread().interrupt();
                Helper.setErrorMessage(ERROR_EXPORT, new Object[] { processTitle }, logger, e);
            }
        }

        String result = asyncThread.getResult();
        if (!result.isEmpty()) {
            if (Objects.nonNull(exportDmsTask)) {
                exportDmsTask.setException(new RuntimeException(processTitle + ": " + result));
            } else {
                Helper.setErrorMessage(processTitle + ": ", result);
            }
        } else {
            if (Objects.nonNull(exportDmsTask)) {
                exportDmsTask.setProgress(100);
            } else {
                Helper.setMessage(process.getTitle() + ": ", "exportFinished");
            }
            // delete success folder again
            if (process.getProject().isDmsImportCreateProcessFolder()) {
                URI successFolder = URI.create(process.getProject().getDmsImportSuccessPath() + "/"
                        + Helper.getNormalizedTitle(processTitle));
                fileService.delete(successFolder);
            }
        }
    }

    private void exportWithoutImport(Process process, LegacyMetsModsDigitalDocumentHelper gdzfile,
            URI destinationDirectory) throws IOException {
        if (MetadataFormat.findFileFormatsHelperByName(
                process.getProject().getFileFormatDmsExport()) == MetadataFormat.METS) {
            writeMetsFile(process, fileService.createResource(destinationDirectory, atsPpnBand + ".xml"), gdzfile);
        } else {
            gdzfile.write(destinationDirectory + atsPpnBand + ".xml");
        }

        Helper.setMessage(process.getTitle() + ": ", "exportFinished");
    }

    /**
     * Get exportDmsTask.
     *
     * @return value of exportDmsTask
     */
    public EmptyTask getExportDmsTask() {
        return exportDmsTask;
    }

    /**
     * Setter method to pass in a task thread to whom progress and error messages
     * shall be reported.
     *
     * @param task
     *            task implementation
     */
    public void setExportDmsTask(EmptyTask task) {
        this.exportDmsTask = task;
    }

    /**
     * Run through all metadata and children of given docstruct to trim the strings
     * calls itself recursively.
     */
    private void trimAllMetadata(LegacyDocStructHelperInterface inStruct) {
        // trim all metadata values
        if (Objects.nonNull(inStruct.getAllMetadata())) {
            for (LegacyMetadataHelper md : inStruct.getAllMetadata()) {
                if (Objects.nonNull(md.getValue())) {
                    md.setStringValue(md.getValue().trim());
                }
            }
        }

        // run through all children of docstruct
        if (Objects.nonNull(inStruct.getAllChildren())) {
            for (LegacyDocStructHelperInterface child : inStruct.getAllChildren()) {
                trimAllMetadata(child);
            }
        }
    }

    /**
     * Download image.
     *
     * @param process
     *            object
     * @param userHome
     *            File
     * @param atsPpnBand
     *            String
     * @param ordnerEndung
     *            String
     */
    public void imageDownload(Process process, URI userHome, String atsPpnBand, final String ordnerEndung)
            throws IOException {
        // determine the source folder
        URI tifOrdner = ServiceManager.getProcessService().getImagesTifDirectory(true, process.getId(),
                process.getTitle(), process.getProcessBaseUri());

        // copy the source folder to the destination folder
        if (fileService.fileExist(tifOrdner) && !fileService.getSubUris(tifOrdner).isEmpty()) {
            URI zielTif = userHome.resolve(atsPpnBand + ordnerEndung + "/");

            /* bei Agora-Import einfach den Ordner anlegen */
            if (process.getProject().isUseDmsImport()) {
                if (!fileService.fileExist(zielTif)) {
                    fileService.createDirectory(userHome, atsPpnBand + ordnerEndung);
                }
            } else {
                // if no async import, then create the folder with user
                // authorization again
                User user = ServiceManager.getUserService().getAuthenticatedUser();
                try {
                    fileService.createDirectoryForUser(zielTif, user.getLogin());
                } catch (IOException e) {
                    handleException(e, process.getTitle());
                    throw e;
                } catch (RuntimeException e) {
                    handleException(e, process.getTitle());
                }
            }

            if (Objects.nonNull(exportDmsTask)) {
                exportDmsTask.setWorkDetail(null);
            }
        }
    }

    private void handleException(Exception e, String processTitle) {
        if (Objects.nonNull(exportDmsTask)) {
            exportDmsTask.setException(e);
            logger.error("Could not create destination directory", e);
        } else {
            Helper.setErrorMessage(ERROR_EXPORT, new Object[] { processTitle }, logger, e);
        }
    }

    /**
     * Starts copying all directories configured as export folder.
     *
     * @param process
     *            object
     * @param destination
     *            the destination directory
     * @throws InterruptedException
     *             if the user clicked stop on the thread running the export DMS
     *             task
     *
     */
    private void directoryDownload(Process process, URI destination) throws IOException, InterruptedException {
        Collection<Subfolder> processDirs = process.getProject().getFolders().parallelStream()
                .filter(Folder::isCopyFolder).map(folder -> new Subfolder(process, folder))
                .collect(Collectors.toList());
        VariableReplacer variableReplacer = new VariableReplacer(null, null, process, null);

        for (Subfolder processDir : processDirs) {
            URI dstDir = destination.resolve(variableReplacer.replace(processDir.getFolder().getRelativePath()));
            fileService.createDirectories(dstDir);

            Collection<URI> srcs = processDir.listContents().values();
            int progress = 0;
            for (URI src : srcs) {
                if (Objects.nonNull(exportDmsTask)) {
                    exportDmsTask.setWorkDetail(fileService.getFileName(src));
                }
                fileService.copyFileToDirectory(src, dstDir);
                if (Objects.nonNull(exportDmsTask)) {
                    exportDmsTask
                            .setProgress((int) ((progress++ + 1) * 98d / processDirs.size() / srcs.size() + 1));
                    if (exportDmsTask.isInterrupted()) {
                        throw new InterruptedException();
                    }
                }
            }
        }
    }
}