package org.goobi.production.flow.statistics.hibernate;
/**
* This file is part of the Goobi Application - a Workflow tool for the support of mass digitization.
*
* Visit the websites for more information.
* - http://www.goobi.org
* - http://launchpad.net/goobi-production
* - http://gdz.sub.uni-goettingen.de
* - http://www.intranda.com
* - http://digiverso.com
*
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Linking this library statically or dynamically with other modules is making a combined work based on this library. Thus, the terms and conditions
* of the GNU General Public License cover the whole combination. As a special exception, the copyright holders of this library give you permission to
* link this library with independent modules to produce an executable, regardless of the license terms of these independent modules, and to copy and
* distribute the resulting executable under terms of your choice, provided that you also meet, for each linked independent module, the terms and
* conditions of the license of that module. An independent module is a module which is not derived from or based on this library. If you modify this
* library, you may extend this exception to your version of the library, but you are not obliged to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.log4j.Logger;
import org.goobi.production.flow.statistics.IDataSource;
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 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 de.sub.goobi.helper.enums.HistoryEventType;
/*****************************************************************************
* 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 = Logger.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 historyEventType
*
* @return status of loops included or not
*/
public Boolean getIncludeLoops() {
return this.flagIncludeLoops;
}
public String getErrMessage() {
return this.errMessage;
}
/**
*
* @returns 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
*/
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;
}
public void setDataSource(IDataSource inSource) {
// gathering IDs from the filter passed by dataSource
try {
this.myIDlist = ((IEvaluableFilter) inSource).getIDList();
} catch (UnsupportedOperationException e) {
logger.warn(e);
}
this.isDirty = true;
}
/**
*
* @returns 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
* (org.goobi.production.flow.statistics.IDataSource)
*/
private DataTable getDataTable() {
if (this.myDataTable != null && !this.isDirty) {
return this.myDataTable;
}
DataTable tableStepCompleted = getAllSteps(HistoryEventType.stepDone);
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
* @return
*/
private DataTable getAllSteps(HistoryEventType 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("");
// if headerRow is set then add it to the DataTable to set columns
// needs to be removed later
if (headerRow != null) {
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);
}
Double count = new Converter(objArr[0]).getDouble();
dataRow.addValue(stepName, count);
} catch (Exception e) {
dataRow.addValue(e.getMessage(), new Double(0));
}
}
}
// to add also the last row
if (dataRow != null) {
dtbl.addDataRow(dataRow);
}
// now removing headerRow
if (headerRow != null) {
dtbl.removeDataRow(headerRow);
}
return dtbl;
}
/*
* (non-Javadoc)
*
* @see
* org.goobi.production.flow.statistics.IStatisticalQuestion#getRendererInverted
* (de.intranda.commons.chart.renderer.IRenderer)
*/
public Boolean getRendererInverted(IRenderer inRenderer) {
return null;
}
public void setCommonWorkflow(List<StepInformation> commonWorkFlow) {
this.commonWorkFlow = commonWorkFlow;
this.isDirty = true;
}
/**
* sets the terminating Step for this view
*
* @param terminatingStep
*/
public void setTerminatingStep(String terminatingStep) {
this.terminatingStep = terminatingStep;
this.isDirty = true;
}
/**
*
* @returns List of Steps that are selectable for this View
*/
public List<String> getSelectableSteps() {
List<String> selectableList = new ArrayList<String>();
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<String>();
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;
}
/**
*
* @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;
}
/**
*
* @returns 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(IDataSource 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;
}
}