/* * Copyright (C) 2013 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 io.seqware.webservice.controller; import com.sun.jersey.api.ConflictException; import com.sun.jersey.api.NotFoundException; import io.seqware.common.model.WorkflowRunStatus; import io.seqware.webservice.generated.controller.WorkflowRunFacadeREST; import io.seqware.webservice.generated.model.File; import io.seqware.webservice.generated.model.Ius; import io.seqware.webservice.generated.model.IusWorkflowRuns; import io.seqware.webservice.generated.model.Lane; import io.seqware.webservice.generated.model.Processing; import io.seqware.webservice.generated.model.ProcessingFiles; import io.seqware.webservice.generated.model.ProcessingIus; import io.seqware.webservice.generated.model.ProcessingRelationship; import io.seqware.webservice.generated.model.SequencerRun; import io.seqware.webservice.generated.model.WorkflowRun; import java.util.Collection; import java.util.HashSet; import java.util.Set; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.persistence.EntityManager; import javax.persistence.LockModeType; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; /** * * @author dyuen */ @Stateless @Path("io.seqware.webservice.model.workflowrun") public class CustomWorkflowRunFacadeREST extends WorkflowRunFacadeREST { /** * The actual delete method, container managed JTA transactions should handle rollback and atomic operations * * @param id * @param victims * @param targetClass */ @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) @DELETE @Path("{id}/rdelete/{targetClass}") @Consumes({ "application/json" }) public void deleteRecursive(@PathParam("id") Integer id, Set<ModelAccessionIDTuple> victims, @PathParam("targetClass") String targetClass) { Set<ModelAccessionIDTuple> victimsFound = handleTargetting(targetClass, id, true, victims); if (victims.equals(victimsFound)) { try { // check that the results are a subset of the matchset for (ModelAccessionIDTuple t : victimsFound) { Object found = getEntityManager().find(Class.forName(t.getAdminModelClass()), t.getId()); getEntityManager().remove(found); } } catch (ClassNotFoundException ex) { throw new NotFoundException(); } } else { throw new ConflictException("keyFile of size " + victims.size() + " does not match " + victimsFound.size() + " found elements were not found, rolling back"); } } /** * Returns the list of potential victims for a deletion operation * * @param id * @param targetClass * @return */ @GET @Path("{id}/rdelete/{targetClass}") @Produces({ "application/json" }) public Set<ModelAccessionIDTuple> findRecursive(@PathParam("id") Integer id, @PathParam("targetClass") String targetClass) { return handleTargetting(targetClass, id, false, null); } /** * Method that starts from a SequencerRun, either deleting appropriate workflow runs or reporting on what would have been deleted * * @param id * @param delete * @param matchSet * @return */ private Set<ModelAccessionIDTuple> deleteSequencerRunRecursive(Integer id, boolean delete, Set<ModelAccessionIDTuple> matchSet) { SequencerRun data = getEntityManager().find(SequencerRun.class, id); if (data == null) { return null; } Set<ModelAccessionIDTuple> results = new HashSet<>(); if (data.getLaneCollection() != null) { for (Lane l : data.getLaneCollection()) { Set<ModelAccessionIDTuple> recursiveSet = this.deleteLaneRecursive(l.getLaneId(), delete, matchSet); if (recursiveSet != null) { results.addAll(recursiveSet); } } } return results; } /** * Method that starts from a lane, either deleting appropriate workflow runs or reporting on what would have been deleted * * @param id * @param delete * @param matchSet * @return */ private Set<ModelAccessionIDTuple> deleteLaneRecursive(Integer id, boolean delete, Set<ModelAccessionIDTuple> matchSet) { Lane data = getEntityManager().find(Lane.class, id); if (data == null) { return null; } Set<ModelAccessionIDTuple> results = new HashSet<>(); if (data.getIusCollection() != null) { for (Ius i : data.getIusCollection()) { Set<ModelAccessionIDTuple> recursiveSet = this.deleteIUSRecursive(i.getIusId(), delete, matchSet); if (recursiveSet != null) { results.addAll(recursiveSet); } } } return results; } /** * Method that starts from an ius, either deleting appropriate workflow runs or reporting on what would have been deleted * * @param id * @param delete * @param matchSet * @return */ private Set<ModelAccessionIDTuple> deleteIUSRecursive(Integer id, boolean delete, Set<ModelAccessionIDTuple> matchSet) { Ius data = getEntityManager().find(Ius.class, id); if (data == null) { return null; } Set<ModelAccessionIDTuple> results = new HashSet<>(); if (data.getIusWorkflowRunsCollection() != null) { for (IusWorkflowRuns iwr : data.getIusWorkflowRunsCollection()) { WorkflowRun workflowRun = iwr.getWorkflowRunId(); Set<ModelAccessionIDTuple> recursiveSet = this.deleteWorkflowRunRecursive(workflowRun.getWorkflowRunId(), delete, matchSet); if (recursiveSet != null) { results.addAll(recursiveSet); } } } if (data.getProcessingIusCollection() != null) { for (ProcessingIus pi : data.getProcessingIusCollection()) { Processing childp = pi.getProcessingId(); handleWorkflowRunGivenProcessing(childp, null, delete, matchSet, results); } } return results; } /** * Method that starts from a workflow run, either deleting appropriate entities or reporting on what would have been deleted * * @param id * primary key id for the workflow run * @param delete * whether to actually go through with deletion * @param matchSet * when actually doing deletion, this set should be a superset of what we find * @return */ private Set<ModelAccessionIDTuple> deleteWorkflowRunRecursive(Integer id, boolean delete, Set<ModelAccessionIDTuple> matchSet) { EntityManager entityManager = getEntityManager(); Set<ModelAccessionIDTuple> results = new HashSet<>(); WorkflowRun data = entityManager.find(WorkflowRun.class, id, LockModeType.OPTIMISTIC); if (data == null) { return null; } // if the workflow run is not in a settled state, then abort if (!(data.getStatus().equals(WorkflowRunStatus.completed.name()) || data.getStatus().equals(WorkflowRunStatus.failed.name()) || data .getStatus().equals(WorkflowRunStatus.cancelled.name()))) { UtilityREST.throwExceptionWithMessage("Unsettled workflow run blocking deletion: " + data.getSwAccession()); } Set<Processing> affectedProcessing = new HashSet<>(); // workflow_run if (data.getProcessingCollection() != null) { affectedProcessing.addAll(data.getProcessingCollection()); } // ancestor_workflow_run if (data.getProcessingCollection1() != null) { affectedProcessing.addAll(data.getProcessingCollection1()); } Set<File> affectedFile = new HashSet<>(); for (Processing p : affectedProcessing) { // look for child workflow runs and handle them recursively if (p.getProcessingRelationshipCollection() != null) { for (ProcessingRelationship childpr : p.getProcessingRelationshipCollection()) { Processing childp = childpr.getChildId(); handleWorkflowRunGivenProcessing(childp, data, delete, matchSet, results); } handleProcessingRelationshipCollection(p.getProcessingRelationshipCollection(), results); handleProcessingRelationshipCollection(p.getProcessingRelationshipCollection1(), results); } Collection<ProcessingFiles> processingFilesCollection = p.getProcessingFilesCollection(); if (processingFilesCollection == null) { continue; } for (ProcessingFiles pf : processingFilesCollection) { affectedFile.add(pf.getFileId()); } } // list all affected resources (and delete them if required) for (Processing p : affectedProcessing) { results.add(new ModelAccessionIDTuple(p.getSwAccession(), p.getProcessingId(), p.getClass().getName())); } for (File f : affectedFile) { results.add(new ModelAccessionIDTuple(f.getSwAccession(), f.getFileId(), f.getClass().getName())); } // handle the workflow run itself results.add(new ModelAccessionIDTuple(data.getSwAccession(), data.getWorkflowRunId(), data.getClass().getName())); return results; } private void handleProcessingRelationshipCollection(Collection<ProcessingRelationship> col, Set<ModelAccessionIDTuple> results) { // add the actual relationships themselves to the results if (col != null) { for (ProcessingRelationship pr : col) { results.add(new ModelAccessionIDTuple(Integer.MAX_VALUE, pr.getProcessingRelationshipId(), pr.getClass().getName())); } } } private Set<ModelAccessionIDTuple> handleTargetting(String targetClass, Integer id, boolean delete, Set<ModelAccessionIDTuple> victims) { if (targetClass == null) { throw new NotFoundException("No targetClass specified"); } if (targetClass.equals(WorkflowRun.class.getSimpleName())) { return deleteWorkflowRunRecursive(id, delete, victims); } else if (targetClass.equals(Ius.class.getSimpleName())) { return deleteIUSRecursive(id, delete, victims); } else if (targetClass.equals(SequencerRun.class.getSimpleName())) { return deleteSequencerRunRecursive(id, delete, victims); } else if (targetClass.equals(Lane.class.getSimpleName())) { return deleteLaneRecursive(id, delete, victims); } else { throw new NotFoundException(); } } private void handleWorkflowRunGivenProcessing(Processing childp, WorkflowRun data, boolean delete, Set<ModelAccessionIDTuple> matchSet, Set<ModelAccessionIDTuple> results) { // if the child processing is connected to a workflow run that is not this one, recursive WorkflowRun childRun = childp.getAncestorWorkflowRunId(); if (childRun == null) { childRun = childp.getWorkflowRunId(); } if (childRun != null && !childRun.equals(data)) { Set<ModelAccessionIDTuple> recursiveSet = this.deleteWorkflowRunRecursive(childRun.getWorkflowRunId(), delete, matchSet); if (recursiveSet != null) { results.addAll(recursiveSet); } } } }