org.kitodo.production.helper.tasks.CreateNewspaperProcessesTask.java Source code

Java tutorial

Introduction

Here is the source code for org.kitodo.production.helper.tasks.CreateNewspaperProcessesTask.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.production.helper.tasks;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.goobi.mq.processors.CreateNewProcessProcessor;
import org.joda.time.LocalDate;
import org.kitodo.data.database.beans.Batch;
import org.kitodo.data.database.beans.Process;
import org.kitodo.data.database.enums.BatchType;
import org.kitodo.data.exceptions.DataException;
import org.kitodo.exceptions.ProcessCreationException;
import org.kitodo.exceptions.ProcessGenerationException;
import org.kitodo.production.forms.ProzesskopieForm;
import org.kitodo.production.helper.Helper;
import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyDocStructHelperInterface;
import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyMetsModsDigitalDocumentHelper;
import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyPrefsHelper;
import org.kitodo.production.model.bibliography.course.Course;
import org.kitodo.production.model.bibliography.course.CourseToGerman;
import org.kitodo.production.model.bibliography.course.Granularity;
import org.kitodo.production.model.bibliography.course.IndividualIssue;
import org.kitodo.production.process.TitleGenerator;
import org.kitodo.production.services.ServiceManager;

/**
 * The class CreateNewspaperProcessesTask is a LongRunningTask to create
 * processes from a course of appearance.
 *
 * @author Matthias Ronge &lt;matthias.ronge@zeutschel.de&gt;
 */
public class CreateNewspaperProcessesTask extends EmptyTask {

    /**
     * The field batchLabel is set in addToBatches() on the first function call
     * which finds it to be null, and is used and set back to null in
     * flushLogisticsBatch() to create the batches specific part of the
     * identifier (put in parentheses behind the shared part).
     */
    private String batchLabel;

    /**
     * The field createBatches holds a granularity level that is used to create
     * batches out of the given processes. The field may be null which disables
     * the feature.
     */
    private final Granularity createBatches;

    /**
     * The field currentBreakMark holds an integer hash value which, for a given
     * Granularity, shall indicate for two neighboring processes whether they
     * form the same logistics batch (break mark is equal) or to different
     * processes (break mark differs).
     */
    private Integer currentBreakMark;

    /**
     * The field fullBatch holds a batch that all issues will be assigned to.
     */
    private Batch fullBatch = new Batch(BatchType.NEWSPAPER);

    /**
     * The field logisticsBatch holds a batch that all issues of the same
     * logistics unit will be assigned to.
     */
    private Batch logisticsBatch = new Batch(BatchType.LOGISTIC);

    /**
     * The field nextProcessToCreate holds the index of the next process to
     * create. Because long running tasks are interruptible is a field so the
     * thread will continue to work with the next process after being continued.
     */
    private int nextProcessToCreate;

    /**
     * The field numberOfProcesses holds the processes size to prevent calling
     * size() over and over again.
     */
    private final int numberOfProcesses;

    /**
     * The field pattern holds a ProzesskopieForm instance that will be used as
     * pattern for the creation of processes.
     */
    private final ProzesskopieForm pattern;

    /**
     * The field processes holds a List of List of IndividualIssue objects that
     * processes will be created from. Each list object, which is a list itself,
     * represents a process to create. Each process can consist of many issues
     * which will be part of that process.
     */
    private final List<List<IndividualIssue>> processes;

    /**
     * The field description holds a verbal description of the course of
     * appearance.
     */
    private final List<String> description;

    /**
     * The class CreateNewspaperProcessesTask is a LongRunningTask to create
     * processes from a course of appearance.
     *
     * @param pattern
     *            a ProzesskopieForm to use for creating processes
     * @param course
     *            course of appearance to create processes for
     * @param batchGranularity
     *            a granularity level at which batches shall be created
     */
    public CreateNewspaperProcessesTask(ProzesskopieForm pattern, Course course, Granularity batchGranularity) {
        super(pattern.getProzessVorlageTitel());
        this.pattern = pattern;
        this.processes = new ArrayList<>(course.getNumberOfProcesses());
        this.description = CourseToGerman.asReadableText(course);
        this.createBatches = batchGranularity;
        for (List<IndividualIssue> issues : course.getProcesses()) {
            List<IndividualIssue> process = new ArrayList<>(issues.size());
            process.addAll(issues);
            processes.add(process);
        }
        nextProcessToCreate = 0;
        numberOfProcesses = processes.size();
    }

    /**
     * The copy constructor creates a new thread from a given one. This is
     * required to call the copy constructor of the parent.
     *
     * @param master
     *            copy master
     */
    public CreateNewspaperProcessesTask(CreateNewspaperProcessesTask master) {
        super(master);
        this.pattern = master.pattern;
        this.processes = master.processes;
        this.description = master.description;
        this.createBatches = master.createBatches;
        this.logisticsBatch = master.logisticsBatch;
        this.currentBreakMark = master.currentBreakMark;
        this.batchLabel = master.batchLabel;
        this.fullBatch = master.fullBatch;
        this.nextProcessToCreate = master.nextProcessToCreate;
        this.numberOfProcesses = master.numberOfProcesses;
    }

    /**
     * The function run() is the main function of this task (which is a thread).
     *
     * <p>
     * It will create a new process for each entry from the field processes?.
     * </p>
     *
     * <p>
     * Therefore it makes use of
     * CreateNewProcessProcessor.newProcessFromTemplate() to once again load a
     * ProzesskopieForm from Hibernate for each process to create, sets the
     * required fields accordingly, then triggers the calculation of the process
     * title and finally initiates the process creation one by one. The
     * statusProgress variable is being updated to show the operator how far the
     * task has proceeded.
     * </p>
     *
     * @see java.lang.Thread#run()
     */
    @Override
    public void run() {
        String currentTitle = null;
        try {
            while (nextProcessToCreate < numberOfProcesses) {
                List<IndividualIssue> issues = processes.get(nextProcessToCreate);
                if (!issues.isEmpty()) {
                    ProzesskopieForm newProcess = CreateNewProcessProcessor
                            .newProcessFromTemplate(pattern.getTemplate().getTitle());
                    newProcess.setDigitalCollections(pattern.getDigitalCollections());
                    newProcess.setDocType(pattern.getDocType());
                    newProcess.setAdditionalFields(pattern.getAdditionalFields());

                    TitleGenerator titleGenerator = new TitleGenerator(newProcess.getAtstsl(),
                            newProcess.getAdditionalFields());
                    try {
                        currentTitle = titleGenerator.generateTitle(newProcess.getTitleDefinition(),
                                issues.get(0).getGenericFields());
                    } catch (ProcessGenerationException e) {
                        setException(new ProcessCreationException(
                                "Couldnt create process title for issue " + issues.get(0).toString(), e));
                        return;
                    }
                    setWorkDetail(currentTitle);

                    if (Objects.isNull(newProcess.getFileformat())) {
                        newProcess.createNewFileformat();
                    }
                    createLogicalStructure(newProcess, issues, StringUtils.join(description, "\n\n"));

                    if (isInterrupted()) {
                        return;
                    }
                    boolean created = newProcess.createProcess();
                    if (!created) {
                        throw new ProcessCreationException(Helper.getLastMessage().replaceFirst(":\\?*$", ""));
                    }
                    addToBatches(newProcess.getProzessKopie(), issues, currentTitle);
                }
                nextProcessToCreate++;
                setProgress((100 * nextProcessToCreate) / (numberOfProcesses + 2));
                if (isInterrupted()) {
                    return;
                }
            }
            flushLogisticsBatch(currentTitle);
            setProgress(((100 * nextProcessToCreate) + 1) / (numberOfProcesses + 2));
            saveFullBatch(currentTitle);
            setProgress(100);
        } catch (DataException | RuntimeException e) {
            String message = Objects.nonNull(currentTitle)
                    ? Helper.getTranslation("createNewspaperProcessesTask.MetadataNotAllowedException",
                            currentTitle)
                    : e.getClass().getSimpleName();
            setException(new ProcessCreationException(message + ": " + e.getMessage(), e));
        }
    }

    /**
     * The function createFirstChild() creates the first level of the logical
     * document structure available at the given parent.
     *
     * @param docStruct
     *            level of the logical document structure to create a child in
     * @param document
     *            document to create the child in
     * @param ruleset
     *            rule set the document is based on
     * @return the created child
     */
    private LegacyDocStructHelperInterface createFirstChild(LegacyDocStructHelperInterface docStruct,
            LegacyMetsModsDigitalDocumentHelper document, LegacyPrefsHelper ruleset) {

        String firstAddable = null;
        try {
            firstAddable = docStruct.getDocStructType().getAllAllowedDocStructTypes().get(0);
            throw new UnsupportedOperationException("Dead code pending removal");
        } catch (RuntimeException e) {
            StringBuilder message = new StringBuilder();
            message.append("Could not add child ");
            if (Objects.nonNull(firstAddable)) {
                message.append(firstAddable);
                message.append(' ');
            }
            message.append("to DocStrct");
            if (Objects.isNull(docStruct.getDocStructType())) {
                message.append(" without type");
            } else {
                message.append("Type ");
                message.append(docStruct.getDocStructType().getName());
            }
            message.append(": ");
            if (e instanceof NullPointerException) {
                message.append("No child type available.");
            } else {
                message.append(e.getClass().getSimpleName());
            }
            throw new ProcessCreationException(message.toString(), e);
        }
    }

    /**
     * Creates a logical structure tree in the process under creation. In the
     * tree, all issues will have been created. Presumption is that never issues
     * for more than one year will be added to the same process.
     *
     * @param newProcess
     *            process under creation
     * @param issues
     *            issues to add
     * @param publicationRun
     *            verbal description of the course of appearance
     */
    private void createLogicalStructure(ProzesskopieForm newProcess, List<IndividualIssue> issues,
            String publicationRun) {

        // initialise
        LegacyPrefsHelper ruleset = ServiceManager.getRulesetService()
                .getPreferences(newProcess.getProzessKopie().getRuleset());
        LegacyMetsModsDigitalDocumentHelper document;
        document = newProcess.getFileformat().getDigitalDocument();
        LegacyDocStructHelperInterface newspaper = document.getLogicalDocStruct();

        // try to add the publication run
        addMetadatum(newspaper, "PublicationRun", publicationRun, false);

        // create the year level
        LegacyDocStructHelperInterface year = createFirstChild(newspaper, document, ruleset);
        String theYear = Integer.toString(issues.get(0).getDate().getYear());
        addMetadatum(year, LegacyMetsModsDigitalDocumentHelper.CREATE_LABEL_ATTRIBUTE_TYPE, theYear, true);

        // create the month level
        Map<Integer, LegacyDocStructHelperInterface> months = new HashMap<>();
        Map<LocalDate, LegacyDocStructHelperInterface> days = new HashMap<>(488);
        for (IndividualIssue individualIssue : issues) {
            LocalDate date = individualIssue.getDate();
            Integer monthNo = date.getMonthOfYear();
            if (!months.containsKey(monthNo)) {
                LegacyDocStructHelperInterface newMonth = createFirstChild(year, document, ruleset);
                addMetadatum(newMonth, LegacyMetsModsDigitalDocumentHelper.CREATE_ORDERLABEL_ATTRIBUTE_TYPE,
                        monthNo.toString(), true);
                addMetadatum(newMonth, year.getDocStructType().getName(), theYear, false);
                addMetadatum(newMonth, LegacyMetsModsDigitalDocumentHelper.CREATE_LABEL_ATTRIBUTE_TYPE,
                        monthNo.toString(), false);
                months.put(monthNo, newMonth);
            }
            LegacyDocStructHelperInterface month = months.get(monthNo);

            // create the day level
            if (!days.containsKey(date)) {
                LegacyDocStructHelperInterface newDay = createFirstChild(month, document, ruleset);
                addMetadatum(newDay, LegacyMetsModsDigitalDocumentHelper.CREATE_ORDERLABEL_ATTRIBUTE_TYPE,
                        Integer.toString(date.getDayOfMonth()), true);
                addMetadatum(newDay, year.getDocStructType().getName(), theYear, false);
                addMetadatum(newDay, month.getDocStructType().getName(), Integer.toString(date.getMonthOfYear()),
                        false);
                addMetadatum(newDay, LegacyMetsModsDigitalDocumentHelper.CREATE_LABEL_ATTRIBUTE_TYPE,
                        Integer.toString(date.getDayOfMonth()), false);
                days.put(date, newDay);
            }
            LegacyDocStructHelperInterface day = days.get(date);

            // create the issue
            LegacyDocStructHelperInterface issue = createFirstChild(day, document, ruleset);
            String heading = individualIssue.getHeading();
            if (Objects.nonNull(heading) && !heading.trim().isEmpty()) {
                addMetadatum(issue, issue.getDocStructType().getName(), heading, true);
            }
            addMetadatum(issue, year.getDocStructType().getName(), theYear, false);
            addMetadatum(issue, month.getDocStructType().getName(), Integer.toString(date.getMonthOfYear()), false);
            addMetadatum(issue, day.getDocStructType().getName(), Integer.toString(date.getDayOfMonth()), false);
            addMetadatum(issue, LegacyMetsModsDigitalDocumentHelper.CREATE_LABEL_ATTRIBUTE_TYPE, heading, false);
        }
    }

    /**
     * The function addMetadatum() adds a metadata to the given level of the
     * logical document structure hierarchy.
     *
     * @param level
     *            level of the logical document structure to create a child in
     * @param key
     *            name of the metadata to create
     * @param value
     *            value to set the metadata to
     * @param fail
     *            if true, throws an error on fail, otherwise returns silently
     */
    private void addMetadatum(LegacyDocStructHelperInterface level, String key, String value, boolean fail) {
        try {
            throw new UnsupportedOperationException("Dead code pending removal");
        } catch (RuntimeException e) {
            if (fail) {
                throw new ProcessCreationException("Could not create metadatum " + key + " in "
                        + (Objects.nonNull(level.getDocStructType())
                                ? "DocStrctType " + level.getDocStructType().getName()
                                : "anonymous DocStrctType")
                        + ": " + e.getClass().getSimpleName().replace("NullPointerException",
                                "No metadata types are associated with that DocStructType."),
                        e);
            }
        }
    }

    /**
     * The method addToBatches() adds a given process to the allover and the
     * annual batch. If the break mark changes, the logistics batch will be
     * flushed and the process will be added to a new logistics batch.
     *
     * @param process
     *            process to add
     * @param issues
     *            list of individual issues in the process
     * @param processTitle
     *            the title of the process
     */
    private void addToBatches(Process process, List<IndividualIssue> issues, String processTitle)
            throws DataException {
        if (Objects.nonNull(createBatches)) {
            int lastIndex = issues.size() - 1;
            int breakMark = issues.get(lastIndex).getBreakMark(createBatches);
            if (Objects.nonNull(currentBreakMark) && breakMark != currentBreakMark) {
                flushLogisticsBatch(processTitle);
            }
            if (Objects.isNull(batchLabel)) {
                batchLabel = createBatches.format(issues.get(lastIndex).getDate());
            }
            logisticsBatch.getProcesses().add(process);
            currentBreakMark = breakMark;
        }
        fullBatch.getProcesses().add(process);
    }

    /**
     * The method flushLogisticsBatch() sets the title for the logistics batch,
     * saves it to hibernate and then populates the global variable with a new,
     * empty batch.
     *
     * @param processTitle
     *            the title of the process
     */
    private void flushLogisticsBatch(String processTitle) throws DataException {
        if (ServiceManager.getBatchService().size(logisticsBatch) > 0) {
            logisticsBatch.setTitle(firstGroupFrom(processTitle) + " (" + batchLabel + ')');
            ServiceManager.getBatchService().save(logisticsBatch);
            logisticsBatch = new Batch(BatchType.LOGISTIC);
        }
        currentBreakMark = null;
        batchLabel = null;
    }

    /**
     * Returns the display name of the task to show to the user.
     *
     * @see org.kitodo.production.helper.tasks.INameableTask#getDisplayName()
     */
    @Override
    public String getDisplayName() {
        return Helper.getTranslation("createNewspaperProcessesTask");
    }

    /**
     * The method saveFullBatch() sets the title for the allover batch and saves
     * it to hibernate.
     *
     * @param theProcessTitle
     *            the title of the process
     */
    private void saveFullBatch(String theProcessTitle) throws DataException {
        fullBatch.setTitle(firstGroupFrom(theProcessTitle));
        ServiceManager.getBatchService().save(fullBatch);
    }

    /**
     * The function firstGroupFrom() extracts the first sequence of characters
     * that are no punctuation characters
     * (<kbd>!&quot;#$%&amp;'()*+,-./:;&lt;=&gt;?@[\]^_`{|}~</kbd>) from the
     * given string.
     *
     * @param s
     *            string to parse
     * @return the first sequence of characters that are no punctuation
     *         characters
     */
    private String firstGroupFrom(String s) {
        final Pattern p = Pattern.compile("^[\\p{Punct}\\p{Space}]*([^\\p{Punct}]+)");
        Matcher m = p.matcher(s);
        if (m.find()) {
            return m.group(1).trim();
        } else {
            return s.trim();
        }
    }

    /**
     * Calls the clone constructor to create a not yet executed instance of this
     * thread object. This is necessary for threads that have terminated in
     * order to render possible to restart them.
     *
     * @return a not-yet-executed replacement of this thread
     * @see org.kitodo.production.helper.tasks.EmptyTask#replace()
     */
    @Override
    public CreateNewspaperProcessesTask replace() {
        return new CreateNewspaperProcessesTask(this);
    }

}