/* * Copyright (C) 2012 SeqWare * * 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 3 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, see <http://www.gnu.org/licenses/>. */ package net.sourceforge.seqware.common.hibernate; import io.seqware.common.model.WorkflowRunStatus; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.Set; import java.util.TreeSet; import net.sourceforge.seqware.common.business.WorkflowRunService; import net.sourceforge.seqware.common.business.WorkflowService; import net.sourceforge.seqware.common.factory.BeanFactory; import net.sourceforge.seqware.common.hibernate.reports.WorkflowRunReportRow; import net.sourceforge.seqware.common.model.File; import net.sourceforge.seqware.common.model.IUS; import net.sourceforge.seqware.common.model.Processing; import net.sourceforge.seqware.common.model.Sample; import net.sourceforge.seqware.common.model.Workflow; import net.sourceforge.seqware.common.model.WorkflowRun; import org.restlet.data.Status; import org.restlet.resource.ResourceException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Reports on workflow run metadata, returning either all workflow runs, all runs of a particular workflow, or a specific workflow run. The * results can be filtered according to Date on the WorkflowRun's createTimestamp. * * @author mtaschuk * @version $Id: $Id */ public class WorkflowRunReport { private final Logger logger = LoggerFactory.getLogger(WorkflowRunReport.class); // set to zero to default to earliest date private Date earliestDate = new Date(0); private Date latestDate = new Date(); private WorkflowRunStatus status = null; /** * Set the value of latestDate. This date must be set prior to calling other methods in order to filter collections of workflow runs. * * @param latestDate * new value of latestDate */ public void setLatestDate(Date latestDate) { this.latestDate = latestDate; } /** * Set the value of earliestDate. This date must be set prior to calling other methods in order to filter collections of workflow runs. * * @param earliestDate * new value of earliestDate */ public void setEarliestDate(Date earliestDate) { this.earliestDate = earliestDate; } /** * Find all of the workflow runs, and report on each of the workflow runs the files, samples and processing events associated with each. * If the latest date and earliest date have been set in this class, the workflow runs will be filtered according to the * createTimestamp. * * @return a collection of workflow run reports */ public Collection<WorkflowRunReportRow> getAllRuns() { WorkflowRunService ws = BeanFactory.getWorkflowRunServiceBean(); List<WorkflowRun> workflowRuns = (List<WorkflowRun>) testIfNull(ws.list()); Collection<WorkflowRunReportRow> rows = runThroughWorkflowRuns(workflowRuns); return rows; } /** * Using a workflow run SWID, report on the files, samples and processing events associated with the run. Setting the earliest and * latest date has no effect on this method. * * @return a workflow run report * @param workflowRunSWID * a {@link java.lang.Integer} object. */ public WorkflowRunReportRow getSingleWorkflowRun(Integer workflowRunSWID) { WorkflowRunService ws = BeanFactory.getWorkflowRunServiceBean(); WorkflowRun workflowRun = (WorkflowRun) testIfNull(ws.findBySWAccession(workflowRunSWID)); logger.debug("Found workflow run: " + workflowRun.getSwAccession()); return fromWorkflowRun(workflowRun); } /** * Using a workflow SWID, find all of the workflow runs, and report on each of the workflow runs the files, samples and processing * events associated with each. If the latest date and earliest date have been set in this class, the workflow runs will be filtered * according to the createTimestamp. * * @param workflowSWID * the SWID of the workflow * @return a collection of workflow run reports */ public Collection<WorkflowRunReportRow> getRunsFromWorkflow(Integer workflowSWID) { WorkflowService ws = BeanFactory.getWorkflowServiceBean(); Workflow w = (Workflow) testIfNull(ws.findBySWAccession(workflowSWID)); Collection<WorkflowRunReportRow> rows = runThroughWorkflowRuns(w.getWorkflowRuns()); return rows; } private Collection<WorkflowRunReportRow> runThroughWorkflowRuns(Collection<WorkflowRun> runs) { List<WorkflowRunReportRow> rows = new ArrayList<>(); for (WorkflowRun wr : runs) { if (earliestDate != null && latestDate != null) { logger.debug("Checking dates: " + earliestDate.toString() + " and " + latestDate.toString()); if (this.status != null && this.status != wr.getStatus()) { logger.debug("Skip workflow run incorrect status: " + wr.getSwAccession()); continue; } if (wr.getCreateTimestamp().after(earliestDate) && wr.getCreateTimestamp().before(latestDate)) { logger.debug("Add new workflow run within dates: " + wr.getSwAccession() + " " + wr.getCreateTimestamp()); rows.add(fromWorkflowRun(wr)); } } else { logger.debug("Add new workflow run: " + wr.getSwAccession()); rows.add(fromWorkflowRun(wr)); } } return rows; } private WorkflowRunReportRow fromWorkflowRun(WorkflowRun workflowRun) { // Immediate parent processings are the processings that occur one level above the workflow run's processings. // Immediate input files are files that have been generated from immediate parent processings. Collection<Processing> processings = collectProcessings(workflowRun); Collection<Processing> allParentProcessings = findParents(processings, workflowRun.getSwAccession(), false); Collection<Processing> immediateParentProcessings = findParents(processings, workflowRun.getSwAccession(), true); Collection<File> allInputFiles = findFiles(allParentProcessings); Collection<File> immediateInputFiles = findFiles(immediateParentProcessings); Collection<File> outputFiles = findFiles(processings); Collection<Processing> useThese; if (!allParentProcessings.isEmpty()) { useThese = allParentProcessings; } else { useThese = processings; } Set<Sample> identitySamples = new HashSet<>(); for (Processing p : useThese) { identitySamples.addAll(findIdentitySamples(p)); } Collection<Sample> librarySamples = findLibrarySamples(identitySamples); String timeSpent = calculateTotalTime(processings); WorkflowRunReportRow wrrr = new WorkflowRunReportRow(); wrrr.setWorkflowRun(workflowRun); wrrr.setIdentitySamples(identitySamples); wrrr.setAllInputFiles(allInputFiles); wrrr.setImmediateInputFiles(immediateInputFiles); wrrr.setOutputFiles(outputFiles); wrrr.setLibrarySamples(librarySamples); wrrr.setParentProcessings(allParentProcessings); wrrr.setWorkflowRunProcessings(allParentProcessings); wrrr.setTimeTaken(timeSpent); return wrrr; } protected Collection<Processing> collectProcessings(WorkflowRun wr) { List<Processing> processings = new ArrayList<>(); WorkflowRun newwr = BeanFactory.getWorkflowRunServiceBean().findByID(wr.getWorkflowRunId()); logger.debug(newwr.getProcessings().size() + " Processings in direct links"); logger.debug(newwr.getOffspringProcessings().size() + " Processings in ancestor links"); processings.addAll(newwr.getProcessings()); processings.addAll(newwr.getOffspringProcessings()); logger.debug(processings.size() + " unique Processings in total"); return processings; } /** * <p> * calculateTotalTime. * </p> * * @param processings * a {@link java.util.Collection} object. * @return a {@link java.lang.String} object. */ public String calculateTotalTime(Collection<Processing> processings) { if (processings.isEmpty()) return ""; Date earlyDate = new Date(Long.MAX_VALUE), lateDate = new Date(0); for (Processing p : processings) { Date processingStart = p.getCreateTimestamp(); if (processingStart.before(earlyDate)) { earlyDate = processingStart; } if (processingStart.after(lateDate)) { lateDate = processingStart; } } long milliseconds = lateDate.getTime() - earlyDate.getTime(); logger.debug("Total time in ms: " + milliseconds); int msPerDay = 86400000; int msPerHour = 3600000; int msPerMinute = 60000; int days = (int) milliseconds / msPerDay; int hourMilliseconds = (int) milliseconds - days * msPerDay; int hours = hourMilliseconds / msPerHour; int minuteMilliseconds = hourMilliseconds - hours * msPerHour; int minutes = minuteMilliseconds / msPerMinute; int secondMilliseconds = minuteMilliseconds - minutes * msPerMinute; double seconds = secondMilliseconds / 1000; StringBuilder time = new StringBuilder(); if (days > 0) { time.append(days).append("d "); } if (hours > 0) { time.append(hours).append("h "); } if (minutes > 0) { time.append(minutes).append("m "); } if (seconds > 0) { time.append(seconds).append("s "); } if (time.length() == 0) { time.append(milliseconds).append(" ms"); } logger.debug("Total time:" + time.toString()); return time.toString(); } protected Collection<File> findFiles(Collection<Processing> processings) { List<File> files = new ArrayList<>(); for (Processing processing : processings) { files.addAll(processing.getFiles()); } logger.debug("Unique files: " + files.size()); return files; } protected Collection<Processing> findParents(Collection<Processing> processings, int workflowRunSWID, boolean findImmediateOnly) { Set<Integer> seenPs = new TreeSet<>(); List<Processing> allParentProcs = new ArrayList<>(); Queue<Processing> queue = new LinkedList<>(); queue.addAll(processings); while (!queue.isEmpty()) { Processing processing = queue.poll(); for (Processing p : processing.getParents()) { // get the workflow run WorkflowRun wr = p.getWorkflowRun(); if (wr == null) { wr = p.getWorkflowRunByAncestorWorkflowRunId(); } if (!seenPs.contains(p.getSwAccession())) { // Add parent processing to queue only if we are traversing the entire tree. if (!findImmediateOnly) queue.offer(p); seenPs.add(p.getSwAccession()); // only add to the parent procs if it's not from the current workflow run if (wr != null && wr.getSwAccession() != workflowRunSWID) { logger.debug("Adding parent processing: " + p.getSwAccession()); allParentProcs.add(p); } else if (wr == null) { logger.debug("Adding processing without workflow run: " + p.getSwAccession()); allParentProcs.add(p); } } } } logger.debug("Number of parent processings:" + allParentProcs.size()); return allParentProcs; } private Collection<Sample> findIdentitySamples(Processing processing) { Set<IUS> iuses = processing.getIUS(); // Set<Lane> lanes = processing.getLanes(); Set<Sample> samples = processing.getSamples(); // logger.debug("iuses: " + iuses.size() + " lanes: " + lanes.size() + " samples: " + samples.size()); List<Sample> allIdentitySamples = new ArrayList<>(); if (iuses != null) { logger.debug("iuses: " + iuses.size()); for (IUS i : iuses) { allIdentitySamples.add(i.getSample()); } } // if (lanes != null) { // for (Lane l : lanes) { // if (l.getIUS() != null) { // for (IUS i : l.getIUS()) { // allIdentitySamples.add(i.getSample()); // } // } // } // } if (samples != null) { logger.debug("samples: " + samples.size()); allIdentitySamples.addAll(samples); } logger.debug("Number of identity samples: " + allIdentitySamples.size()); return allIdentitySamples; } private Collection<Sample> findLibrarySamples(Collection<Sample> allIdentitySamples) { List<Sample> allLibrarySamples = new ArrayList<>(); Set<Integer> seenSams = new TreeSet<>(); Queue<Sample> queue = new LinkedList<>(); queue.addAll(allIdentitySamples); while (!queue.isEmpty()) { Sample sample = queue.poll(); for (Sample p : sample.getParents()) { // add to the queue if we haven't seen it before if (!seenSams.contains(p.getSwAccession())) { queue.offer(p); seenSams.add(p.getSwAccession()); // only add to the library samples if it is a root node if (p.getParents() == null || p.getParents().isEmpty()) { logger.debug("Adding library sample: " + p.toString()); allLibrarySamples.add(p); } } } } logger.debug("Number of library samples: " + allLibrarySamples.size()); return allLibrarySamples; } /** * <p> * testIfNull. * </p> * * @param o * a {@link java.lang.Object} object. * @return a {@link java.lang.Object} object. */ protected Object testIfNull(Object o) { if (o == null) { throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND); } return o; } public void setStatus(WorkflowRunStatus status) { this.status = status; } public Collection<WorkflowRunReportRow> getRunsByStatus(WorkflowRunStatus status) { WorkflowRunService ws = BeanFactory.getWorkflowRunServiceBean(); List<WorkflowRun> runsWithValidStatus = ws.findByCriteria("wr.status = '" + status.toString() + "'"); Collection<WorkflowRunReportRow> rows = runThroughWorkflowRuns(runsWithValidStatus); return rows; } }