org.goobi.production.flow.statistics.hibernate.StatQuestProjectProgressData.java Source code

Java tutorial

Introduction

Here is the source code for org.goobi.production.flow.statistics.hibernate.StatQuestProjectProgressData.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.goobi.production.flow.statistics.hibernate;

import de.intranda.commons.chart.renderer.ChartRenderer;
import de.intranda.commons.chart.renderer.IRenderer;
import de.intranda.commons.chart.results.DataRow;
import de.intranda.commons.chart.results.DataTable;
import de.sub.goobi.helper.Helper;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.goobi.production.flow.statistics.IStatisticalQuestion;
import org.goobi.production.flow.statistics.IStatisticalQuestionLimitedTimeframe;
import org.goobi.production.flow.statistics.StepInformation;
import org.goobi.production.flow.statistics.enums.CalculationUnit;
import org.goobi.production.flow.statistics.enums.TimeUnit;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.type.StandardBasicTypes;
import org.joda.time.DateTime;
import org.kitodo.data.database.helper.enums.HistoryTypeEnum;
import org.kitodo.dto.BaseDTO;
import org.kitodo.dto.ProcessDTO;

/**
 * Imlpementation of {@link IStatisticalQuestion}. This is used for the
 * generation of a Datatable relfecting the progress of a project, based on it's
 * processes workflow. Only the workflow common to all processes is used. A
 * reference step is taken and it's progress is calculated against the average
 * throughput. The average throughput is based on the duration and volume of a
 * project.
 * 
 * @author Wulf Riebensahm
 */
public class StatQuestProjectProgressData implements IStatisticalQuestionLimitedTimeframe, Serializable {

    private static final long serialVersionUID = 5488469945490611200L;
    private static final Logger logger = LogManager.getLogger(StatQuestProjectProgressData.class);
    private Date timeFilterFrom;
    private TimeUnit timeGrouping = TimeUnit.months;
    private Date timeFilterTo;
    private List<Integer> myIDlist;
    private Boolean flagIncludeLoops = false;
    private String terminatingStep; // stepDone title
    private List<String> selectedSteps;
    private Double requiredDailyOutput;
    private Boolean flagReferenceCurve = false;
    private List<StepInformation> commonWorkFlow = null;
    private DataTable myDataTable = null;
    private String errMessage;
    private boolean isDirty = true;

    /**
     * Loops included means that all step open all stepdone are considered loops not
     * included means that only min(date) or max(date) - depending on option in.
     *
     * @see HistoryTypeEnum
     *
     * @return status of loops included or not
     */
    public Boolean getIncludeLoops() {
        return this.flagIncludeLoops;
    }

    public String getErrMessage() {
        return this.errMessage;
    }

    /**
     * Check if data is complete.
     *
     * @return true if all Data for the generation is set
     */

    public Boolean isDataComplete() {
        Boolean error = false;
        if (this.timeFilterFrom == null) {
            logger.debug("time from is not set");
            error = true;
        }
        if (this.timeFilterTo == null) {
            logger.debug("time to is not set");
            error = true;
        }
        if (this.requiredDailyOutput == null) {
            logger.debug("daily output is not set");
            error = true;
        }
        if (this.terminatingStep == null) {
            logger.debug("terminating step is not set");
            error = true;
        }
        if (this.myIDlist == null) {
            logger.debug("processes filter is not set");
            error = true;
        }
        return !error;
    }

    public void setReferenceCurve(Boolean flagIn) {
        if (flagIn == null) {
            this.flagReferenceCurve = false;
        } else {
            this.flagReferenceCurve = flagIn;
        }
        this.isDirty = true;
    }

    public void setRequiredDailyOutput(Double requiredDailyOutput) {
        this.requiredDailyOutput = requiredDailyOutput;
        this.isDirty = true;
    }

    /**
     * Set status of loops included.
     *
     * @param includeLoops
     *            as Boolean
     */
    public void setIncludeLoops(Boolean includeLoops) {
        this.flagIncludeLoops = includeLoops;
    }

    /*
     * generate requiredOutputLine
     */
    private DataRow requiredOutput() {
        DataRow dataRow = new DataRow(Helper.getTranslation("requiredOutput"));
        dataRow.setShowPoint(false);

        Double requiredOutputPerTimeUnit = this.requiredDailyOutput * this.timeGrouping.getDayFactor();

        // assembling a requiredOutputRow from the labels in the reference row
        // and the calculated requiredOutputPerTimeUnit
        for (String title : this.timeGrouping.getDateRow(this.timeFilterFrom, this.timeFilterTo)) {
            dataRow.addValue(title, requiredOutputPerTimeUnit);
        }
        return dataRow;

    }

    /*
     * generate referenceCurve
     */
    private DataRow referenceCurve(DataRow referenceRow) {
        DataRow orientationRow = requiredOutput();
        DataRow dataRow = new DataRow(Helper.getTranslation("ReferenceCurve"));
        dataRow.setShowPoint(false);
        // may have to be calculated differently

        Integer count = orientationRow.getNumberValues();

        Double remainingOutput = this.requiredDailyOutput * this.timeGrouping.getDayFactor() * count;
        Double remainingAverageOutput = remainingOutput / count;

        // the way this is calculated is by subtracting each value from the
        // total remaining output
        // and calculating the averageOutput based on the remaining output and
        // the remaining periods
        for (int i = 0; i < orientationRow.getNumberValues(); i++) {
            dataRow.addValue(orientationRow.getLabel(i), remainingAverageOutput);
            Double doneValue = referenceRow.getValue(orientationRow.getLabel(i));
            if (doneValue != null) {
                remainingOutput = remainingOutput - doneValue;
            }
            count--;
            Date breakOffDate = new DateTime(this.timeFilterFrom)
                    .plusDays((int) (i * this.timeGrouping.getDayFactor())).toDate();
            if (breakOffDate.before(new Date())) {
                remainingAverageOutput = remainingOutput / count;
            }
        }

        return dataRow;
    }

    /**
     * Set data source.
     * 
     * @param source
     *            list of DTO objects
     */
    public void setDataSource(List<? extends BaseDTO> source) {
        // gathering IDs from the filter passed by dataSource
        this.myIDlist = getIds(source);
        this.isDirty = true;
    }

    @SuppressWarnings("unchecked")
    private List<Integer> getIds(List<? extends BaseDTO> dataSource) {
        List<Integer> ids = new ArrayList<>();
        for (ProcessDTO process : (List<ProcessDTO>) dataSource) {
            ids.add(process.getId());
        }
        return ids;
    }

    /**
     * Get reference curve.
     * 
     * @return if reference curve is used of average production
     */
    public Boolean getReferenceCurve() {
        return this.flagReferenceCurve;
    }

    public DataRow getRefRow() {
        if (this.flagReferenceCurve) {
            return referenceCurve(getDataRow(this.terminatingStep));
        } else {
            return requiredOutput();
        }
    }

    public DataRow getDataRow(String stepName) {
        Boolean flagNoContent = true;
        for (int i = 0; i < getDataTable().getDataRows().size(); i++) {
            flagNoContent = false;
            DataRow dr = getDataTable().getDataRows().get(i);
            if (dr.getName().equals(stepName)) {
                return dr;
            }
        }
        // TODO: Retireve from Messages
        String message = "couldn't retrieve requested DataRow by name '" + stepName + "'";
        if (flagNoContent) {
            message = message + " - empty DataTable";
        }

        logger.error(message);
        return null;

    }

    /*
     * (non-Javadoc)
     *
     * @see org.goobi.production.flow.statistics.IStatisticalQuestion#getDataTables
     * (List)
     */
    private DataTable getDataTable() {
        if (this.myDataTable != null && !this.isDirty) {
            return this.myDataTable;
        }

        DataTable tableStepCompleted = getAllSteps(HistoryTypeEnum.taskDone);

        tableStepCompleted.setUnitLabel(Helper.getTranslation(this.timeGrouping.getSingularTitle()));
        tableStepCompleted.setName(Helper.getTranslation("doneSteps"));

        // show in line graph
        tableStepCompleted.setShowableInChart(true);
        tableStepCompleted.setShowableInTable(false);
        tableStepCompleted.setShowableInPieChart(false);
        tableStepCompleted = tableStepCompleted.getDataTableInverted();

        this.myDataTable = tableStepCompleted;
        this.isDirty = false;
        return tableStepCompleted;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.goobi.production.flow.statistics.IStatisticalQuestion#
     * setCalculationUnit
     * (org.goobi.production.flow.statistics.enums.CalculationUnit)
     */
    @Override
    public void setCalculationUnit(CalculationUnit cu) {
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.goobi.production.flow.statistics.IStatisticalQuestionLimitedTimeframe
     * #setTimeFrame(java.util.Date, java.util.Date)
     */
    @Override
    public void setTimeFrame(Date timeFrom, Date timeTo) {
        this.timeFilterFrom = timeFrom;
        this.timeFilterTo = timeTo;
        this.isDirty = true;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.goobi.production.flow.statistics.IStatisticalQuestion#
     * isRendererInverted (de.intranda.commons.chart.renderer.IRenderer)
     */
    @Override
    public Boolean isRendererInverted(IRenderer inRenderer) {
        return inRenderer instanceof ChartRenderer;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.goobi.production.flow.statistics.IStatisticalQuestion#
     * getNumberFormatPattern()
     */
    @Override
    public String getNumberFormatPattern() {
        return "#";
    }

    /**
     * Returns a DataTable populated with the specified events.
     *
     * @param requestedType
     *            as HistoryTypeEnum
     * @return DataTable with all tasks
     */
    private DataTable getAllSteps(HistoryTypeEnum requestedType) {

        // adding time restrictions
        String natSQL = new SQLStepRequestByName(this.timeFilterFrom, this.timeFilterTo, this.timeGrouping,
                this.myIDlist).getSQL(requestedType, null, true, this.flagIncludeLoops);

        return buildDataTableFromSQL(natSQL);
    }

    /**
     * Method generates a DataTable based on the input SQL. Methods success is
     * depending on a very specific data structure ... so don't use it if you don't
     * exactly understand it
     *
     *
     * @param natSQL
     *            headerFromSQL -> to be used, if headers need to be read in first
     *            in order to get a certain sorting
     * @return DataTable
     */
    private DataTable buildDataTableFromSQL(String natSQL) {
        Session session = Helper.getHibernateSession();

        if (this.commonWorkFlow == null) {
            return null;
        }

        DataRow headerRow = new DataRow("Header - delete again");

        for (StepInformation step : this.commonWorkFlow) {
            String stepName = step.getTitle();
            headerRow.setName("header - delete again");
            headerRow.addValue(stepName, Double.parseDouble("0"));
        }

        SQLQuery query = session.createSQLQuery(natSQL);

        // needs to be there otherwise an exception is thrown
        query.addScalar("stepCount", StandardBasicTypes.DOUBLE);
        query.addScalar("stepName", StandardBasicTypes.STRING);
        query.addScalar("intervall", StandardBasicTypes.STRING);

        @SuppressWarnings("rawtypes")
        List list = query.list();

        DataTable dtbl = new DataTable("");

        // Set columns to be removed later.
        dtbl.addDataRow(headerRow);

        DataRow dataRow = null;

        // each data row comes out as an Array of Objects
        // the only way to extract the data is by knowing
        // in which order they come out

        // checks if intervall has changed which then triggers the start for a
        // new row
        // intervall here is the timeGroup Expression (e.g. "2006/05" or
        // "2006-10-05")
        String observeIntervall = "";

        for (Object obj : list) {
            Object[] objArr = (Object[]) obj;
            String stepName = new Converter(objArr[1]).getString();
            if (isInWorkFlow(stepName)) {
                try {
                    String intervall = new Converter(objArr[2]).getString();

                    if (!observeIntervall.equals(intervall)) {
                        observeIntervall = intervall;

                        // row cannot be added before it is filled because the
                        // add process triggers
                        // a testing for header alignement -- this is where we
                        // add it after iterating it first
                        if (dataRow != null) {
                            dtbl.addDataRow(dataRow);
                        }

                        // setting row name with localized time group and the
                        // date/time extraction based on the group
                        dataRow = new DataRow(intervall);
                    }
                    if (dataRow != null) {
                        Double count = new Converter(objArr[0]).getDouble();
                        dataRow.addValue(stepName, count);
                    }

                } catch (Exception e) {
                    if (dataRow != null) {
                        dataRow.addValue(e.getMessage(), 0.0);
                    }
                }
            }
        }
        // to add also the last row
        if (dataRow != null) {
            dtbl.addDataRow(dataRow);
        }

        // now removing headerRow
        dtbl.removeDataRow(headerRow);

        return dtbl;
    }

    public void setCommonWorkflow(List<StepInformation> commonWorkFlow) {
        this.commonWorkFlow = commonWorkFlow;
        this.isDirty = true;
    }

    /**
     * Sets the terminating Step for this view.
     *
     * @param terminatingStep
     *            as String
     */
    public void setTerminatingStep(String terminatingStep) {
        this.terminatingStep = terminatingStep;
        this.isDirty = true;
    }

    /**
     * Get selectable tasks.
     * 
     * @return List of Steps that are selectable for this View
     */

    public List<String> getSelectableSteps() {
        List<String> selectableList = new ArrayList<>();
        selectableList.add(Helper.getTranslation("selectAll"));
        for (StepInformation steps : this.commonWorkFlow) {
            selectableList.add(steps.getTitle());
        }
        return selectableList;
    }

    public void setSelectedSteps(List<String> inSteps) {
        this.isDirty = true;
        if (inSteps.contains(Helper.getTranslation("selectAll"))) {
            this.selectedSteps = new ArrayList<>();
            for (StepInformation steps : this.commonWorkFlow) {
                this.selectedSteps.add(steps.getTitle());
                this.terminatingStep = steps.getTitle();
            }
        } else {
            this.selectedSteps = inSteps;
            if (inSteps.size() > 0) {
                this.terminatingStep = inSteps.get(inSteps.size() - 1);
            }
        }
    }

    public List<String> getSelectedSteps() {
        return this.selectedSteps;
    }

    /**
     * Get selectable time units.
     *
     * @return list of Timeunits to select
     */
    public List<TimeUnit> getSelectableTimeUnits() {
        return TimeUnit.getAllVisibleValues();
    }

    /*
     * checks if testString is contained in workflow
     */
    private Boolean isInWorkFlow(String testString) {
        for (StepInformation step : this.commonWorkFlow) {
            if (step.getTitle().equals(testString)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get selected tables.
     *
     * @return DataTable generated from the selected step names and the selected
     *         reference curve
     */
    public DataTable getSelectedTable() {
        getDataTable();
        DataTable returnTable = new DataTable(this.terminatingStep);
        returnTable.addDataRow(getRefRow());
        for (String stepTitle : this.selectedSteps) {
            returnTable.addDataRow(getDataRow(stepTitle));
        }
        // rest this, so that unit knows that no changes were made in between
        // calls
        return returnTable;
    }

    @Override
    public List<DataTable> getDataTables(List dataSource) {
        return null;
    }

    public boolean hasChanged() {
        return this.isDirty;
    }

    public TimeUnit getTimeUnit() {
        return this.timeGrouping;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.goobi.production.flow.statistics.IStatisticalQuestion#setTimeUnit
     * (org.goobi.production.flow.statistics.enums.TimeUnit)
     */
    @Override
    public void setTimeUnit(TimeUnit timeUnit) {
        this.isDirty = true;
        this.timeGrouping = timeUnit;
    }
}