X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;ds=sidebyside;f=src%2Fjalview%2Fws2%2Factions%2Fannotation%2FAnnotationTask.java;h=6cb5aa67510e77c9818f17d953e66d6a2b6c5ba6;hb=a6aaf54e567106178f3f6733b59674f13522b919;hp=8f210e088b32047b7a3a44e115d6ee84f1455ffb;hpb=9c32e2ee08ecb3fa8d858b1b47f8854a3d7a2f95;p=jalview.git diff --git a/src/jalview/ws2/actions/annotation/AnnotationTask.java b/src/jalview/ws2/actions/annotation/AnnotationTask.java index 8f210e0..6cb5aa6 100644 --- a/src/jalview/ws2/actions/annotation/AnnotationTask.java +++ b/src/jalview/ws2/actions/annotation/AnnotationTask.java @@ -2,18 +2,12 @@ package jalview.ws2.actions.annotation; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import jalview.analysis.AlignmentAnnotationUtils; -import jalview.api.AlignCalcManagerI2; -import jalview.api.AlignCalcWorkerI; import jalview.api.AlignViewportI; import jalview.api.FeatureColourI; -import jalview.api.PollableAlignCalcWorkerI; -import jalview.bin.Cache; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; import jalview.datamodel.AnnotatedCollectionI; @@ -22,560 +16,188 @@ import jalview.datamodel.ContiguousI; import jalview.datamodel.Mapping; import jalview.datamodel.SequenceI; import jalview.datamodel.features.FeatureMatcherSetI; -import jalview.schemes.FeatureSettingsAdapter; -import jalview.util.ArrayUtils; import jalview.util.MapList; -import jalview.util.MathUtils; -import jalview.util.Pair; -import jalview.workers.AlignCalcWorker; import jalview.ws.params.ArgumentI; -import jalview.ws2.actions.AbstractPollableTask; -import jalview.ws2.actions.BaseJob; +import jalview.ws2.actions.BaseTask; import jalview.ws2.actions.ServiceInputInvalidException; -import jalview.ws2.actions.api.JobI; -import jalview.ws2.actions.api.TaskEventListener; -import jalview.ws2.actions.api.TaskI; import jalview.ws2.api.Credentials; import jalview.ws2.api.JobStatus; -import jalview.ws2.api.WebServiceJobHandle; import jalview.ws2.client.api.AnnotationWebServiceClientI; -import jalview.ws2.helpers.DelegateJobEventListener; -import jalview.ws2.helpers.TaskEventSupport; -import static java.util.Objects.requireNonNullElse; - -public class AnnotationTask implements TaskI +public class AnnotationTask + extends BaseTask { - private final long uid = MathUtils.getUID(); - private AnnotationWebServiceClientI client; private final AnnotationAction action; - private final List args; - - private final Credentials credentials; - - private final AlignViewportI viewport; - - private final TaskEventSupport eventHandler; - - private JobStatus taskStatus = null; - - private AlignCalcWorkerAdapter worker = null; - - private List jobs = Collections.emptyList(); - - private AnnotationResult result = null; - - private DelegateJobEventListener jobEventHandler; - - private class AlignCalcWorkerAdapter extends AlignCalcWorker - implements PollableAlignCalcWorkerI - { - private boolean restarting = false; - - AlignCalcWorkerAdapter(AlignCalcManagerI2 calcMan) - { - super(viewport, null); - this.calcMan = calcMan; - } - - String getServiceName() - { - return action.getWebService().getName(); - } - - @Override - public void startUp() throws Throwable - { - if (alignViewport.isClosed()) - { - stop(); - throw new IllegalStateException("Starting annotation for closed viewport"); - } - if (restarting) - eventHandler.fireTaskRestarted(); - else - restarting = true; - jobs = Collections.emptyList(); - try - { - jobs = prepare(); - } catch (ServiceInputInvalidException e) - { - setStatus(JobStatus.INVALID); - eventHandler.fireTaskException(e); - throw e; - } - setStatus(JobStatus.READY); - eventHandler.fireTaskStarted(jobs); - for (var job : jobs) - { - job.addPropertyChagneListener(jobEventHandler); - } - try - { - startJobs(); - } catch (IOException e) - { - eventHandler.fireTaskException(e); - cancelJobs(); - setStatus(JobStatus.SERVER_ERROR); - throw e; - } - setStatus(JobStatus.SUBMITTED); - } - - @Override - public boolean poll() throws Throwable - { - boolean done = AnnotationTask.this.poll(); - updateGlobalStatus(); - if (done) - { - retrieveAndProcessResult(); - eventHandler.fireTaskCompleted(result); - } - return done; - } - - private void retrieveAndProcessResult() throws IOException - { - result = retrieveResult(); - updateOurAnnots(result.annotations); - if (result.transferFeatures) - { - final var featureColours = result.featureColours; - final var featureFilters = result.featureFilters; - viewport.applyFeaturesStyle(new FeatureSettingsAdapter() - { - @Override - public FeatureColourI getFeatureColour(String type) - { - return featureColours.get(type); - } + private final AlignmentI alignment; - @Override - public FeatureMatcherSetI getFeatureFilters(String type) - { - return featureFilters.get(type); - } - - @Override - public boolean isFeatureDisplayed(String type) - { - return featureColours.containsKey(type); - } - }); - } - } - - @Override - public void updateAnnotation() - { - var job = jobs.size() > 0 ? jobs.get(0) : null; - if (!calcMan.isWorking(this) && job != null) - { - var ret = updateResultAnnotation(job, job.returnedAnnotations); - updateOurAnnots(ret.get0()); - } - } - - private void updateOurAnnots(List newAnnots) - { - List oldAnnots = requireNonNullElse(ourAnnots, Collections.emptyList()); - ourAnnots = newAnnots; - AlignmentI alignment = viewport.getAlignment(); - for (AlignmentAnnotation an : oldAnnots) - { - if (!newAnnots.contains(an)) - { - alignment.deleteAnnotation(an); - } - } - oldAnnots.clear(); - for (AlignmentAnnotation an : ourAnnots) - { - viewport.getAlignment().validateAnnotation(an); - } - } - - @Override - public void cancel() - { - cancelJobs(); - } - - void stop() - { - super.abortAndDestroy(); - } - - @Override - public void done() - { - for (var job : jobs) - { - if (job.isInputValid() && !job.isCompleted()) - { - /* if done was called but job is not completed then it - * must have been stopped by an exception */ - job.setStatus(JobStatus.SERVER_ERROR); - } - } - updateGlobalStatus(); - // dispose of unfinished jobs just in case - cancelJobs(); - } - } + private final AnnotatedCollectionI selectionGroup; public AnnotationTask(AnnotationWebServiceClientI client, - AnnotationAction action, List args, Credentials credentials, - AlignViewportI viewport, - TaskEventListener eventListener) + AnnotationAction action, List args, + Credentials credentials, AlignViewportI viewport) { + super(client, args, credentials); this.client = client; this.action = action; - this.args = args; - this.credentials = credentials; - this.viewport = viewport; - this.eventHandler = new TaskEventSupport<>(this, eventListener); - this.jobEventHandler = new DelegateJobEventListener<>(this.eventHandler); - } - - @Override - public long getUid() - { - return uid; - } - - public void start(AlignCalcManagerI2 calcManager) - { - if (this.worker != null) - throw new IllegalStateException("task already started"); - this.worker = new AlignCalcWorkerAdapter(calcManager); - if (taskStatus != JobStatus.CANCELLED) - { - List oldWorkers = calcManager.getWorkersOfClass( - AlignCalcWorkerAdapter.class); - for (var worker : oldWorkers) - { - if (action.getWebService().getName().equalsIgnoreCase( - ((AlignCalcWorkerAdapter) worker).getServiceName())) - { - // remove interactive workers for the same service. - calcManager.removeWorker(worker); - calcManager.cancelWorker(worker); - } - } - if (action.getWebService().isInteractive()) - calcManager.registerWorker(worker); - else - calcManager.startWorker(worker); - } - } - - /* - * The following methods are mostly copied from the {@link AbstractPollableTask} - * TODO: move common functionality to a base class - */ - @Override - public JobStatus getStatus() - { - return taskStatus; - } - - private void setStatus(JobStatus status) - { - if (this.taskStatus != status) - { - Cache.log.debug(String.format("%s status change to %s", this, status.name())); - this.taskStatus = status; - eventHandler.fireTaskStatusChanged(status); - } - } - - private void updateGlobalStatus() - { - int precedence = -1; - for (BaseJob job : jobs) - { - JobStatus status = job.getStatus(); - int jobPrecedence = ArrayUtils.indexOf(JobStatus.statusPrecedence, status); - if (precedence < jobPrecedence) - precedence = jobPrecedence; - } - if (precedence >= 0) - { - setStatus(JobStatus.statusPrecedence[precedence]); - } - } - - @Override - public List getSubJobs() - { - return jobs; + this.alignment = viewport.getAlignment(); + this.selectionGroup = viewport.getSelectionGroup(); } /** * Create and return a list of annotation jobs from the current state of the * viewport. Returned job are not started by this method and should be stored * in a field and started separately. - * + * * @return list of annotation jobs * @throws ServiceInputInvalidException * input data is not valid */ - private List prepare() throws ServiceInputInvalidException + @Override + public List prepareJobs() + throws ServiceInputInvalidException { - AlignmentI alignment = viewport.getAlignment(); - if (alignment == null || alignment.getWidth() <= 0 || - alignment.getSequences() == null) - throw new ServiceInputInvalidException("Alignment does not contain sequences"); - if (alignment.isNucleotide() && !action.doAllowNucleotide()) + if (alignment == null || alignment.getWidth() <= 0 + || alignment.getSequences() == null) throw new ServiceInputInvalidException( - action.getFullName() + " does not allow nucleotide sequences"); + "Alignment does not contain sequences"); + if (alignment.isNucleotide() && !action.doAllowNucleotide()) + throw new ServiceInputInvalidException(action.getFullName() + + " does not allow nucleotide sequences"); if (!alignment.isNucleotide() && !action.doAllowProtein()) throw new ServiceInputInvalidException( - action.getFullName() + " does not allow protein sequences"); + action.getFullName() + " does not allow protein sequences"); boolean bySequence = !action.isAlignmentAnalysis(); - AnnotatedCollectionI inputSeqs = bySequence ? viewport.getSelectionGroup() : null; - if (inputSeqs == null || inputSeqs.getWidth() <= 0 || - inputSeqs.getSequences() == null || inputSeqs.getSequences().size() < 1) + AnnotatedCollectionI inputSeqs = bySequence ? selectionGroup : null; + if (inputSeqs == null || inputSeqs.getWidth() <= 0 + || inputSeqs.getSequences() == null + || inputSeqs.getSequences().size() < 1) inputSeqs = alignment; boolean submitGaps = action.isAlignmentAnalysis(); boolean requireAligned = action.getRequireAlignedSequences(); boolean filterSymbols = action.getFilterSymbols(); int minSize = action.getMinSequences(); AnnotationJob job = AnnotationJob.create(inputSeqs, bySequence, - submitGaps, requireAligned, filterSymbols, minSize); + submitGaps, requireAligned, filterSymbols, minSize); if (!job.isInputValid()) { job.setStatus(JobStatus.INVALID); - throw new ServiceInputInvalidException("Annotation job has invalid input"); + throw new ServiceInputInvalidException( + "Annotation job has invalid input"); } job.setStatus(JobStatus.READY); return List.of(job); } - private void startJobs() throws IOException - { - for (BaseJob job : jobs) - { - if (job.isInputValid() && job.getStatus() == JobStatus.READY) - { - var serverJob = client.submit(job.getInputSequences(), - args, credentials); - job.setServerJob(serverJob); - job.setStatus(JobStatus.SUBMITTED); - } - } - } - - private boolean poll() throws IOException - { - boolean allDone = true; - for (BaseJob job : jobs) - { - if (job.isInputValid() && !job.getStatus().isDone()) - { - WebServiceJobHandle serverJob = job.getServerJob(); - job.setStatus(client.getStatus(serverJob)); - job.setLog(client.getLog(serverJob)); - job.setErrorLog(client.getErrorLog(serverJob)); - } - allDone &= job.isCompleted(); - } - return allDone; - } - - private AnnotationResult retrieveResult() throws IOException + @Override + protected AnnotationResult collectResult(List jobs) + throws IOException { final Map featureColours = new HashMap<>(); final Map featureFilters = new HashMap<>(); var job = jobs.get(0); List returnedAnnot = client.attachAnnotations( - job.getServerJob(), job.getInputSequences(), featureColours, - featureFilters); + job.getServerJob(), job.getInputSequences(), featureColours, + featureFilters); /* TODO * copy over each annotation row returned and also defined on each * sequence, excluding regions not annotated due to gapMap/column * visibility */ - // update calcId if it is not already set on returned annotation - for (AlignmentAnnotation annot : returnedAnnot) + udpateCalcId(returnedAnnot); + for (AlignmentAnnotation ala : returnedAnnot) { - if (annot.getCalcId() == null || annot.getCalcId().isEmpty()) + SequenceI seq = (ala.sequenceRef == null) ? null + : job.seqNames.get(ala.sequenceRef.getName()); + if (job.gapMap != null && job.gapMap.length > 0) + ala.annotations = createGappedAnnotations(ala.annotations, + job.gapMap); + if (seq != null) { - annot.setCalcId(action.getFullName()); + int startRes = seq.findPosition(job.regionStart); + ala.createSequenceMapping(seq, startRes, false); } - annot.autoCalculated = action.isAlignmentAnalysis() && - action.getWebService().isInteractive(); } - job.returnedAnnotations = returnedAnnot; - job.featureColours = featureColours; - job.featureFilters = featureFilters; - var ret = updateResultAnnotation(job, returnedAnnot); - var annotations = ret.get0(); - var transferFeatures = ret.get1(); - return new AnnotationResult(annotations, transferFeatures, featureColours, - featureFilters); - } - private Pair, Boolean> updateResultAnnotation( - AnnotationJob job, List annotations) - { - List newAnnots = new ArrayList<>(); - // update graphGroup for all annotation - /* find a graphGroup greater than any existing one, could be moved - * to Alignment#getNewGraphGroup() - returns next unused graph group */ - int graphGroup = 1; - if (viewport.getAlignment().getAlignmentAnnotation() != null) - { - for (var ala : viewport.getAlignment().getAlignmentAnnotation()) - { - graphGroup = Math.max(graphGroup, ala.graphGroup); - } - } - // update graphGroup in the annotation rows returned form service' - /* TODO: look at sequence annotation rows and update graph groups in the - * case of reference annotation */ - for (AlignmentAnnotation ala : annotations) - { - if (ala.graphGroup > 0) - ala.graphGroup += graphGroup; - SequenceI aseq = null; - // transfer sequence refs and adjust gapMap - if (ala.sequenceRef != null) - { - aseq = job.seqNames.get(ala.sequenceRef.getName()); - } - ala.sequenceRef = aseq; - - Annotation[] resAnnot = ala.annotations; - boolean[] gapMap = job.gapMap; - Annotation[] gappedAnnot = new Annotation[Math.max( - viewport.getAlignment().getWidth(), gapMap.length)]; - for (int p = 0, ap = job.start; ap < gappedAnnot.length; ap++) - { - if (gapMap.length > ap && !gapMap[ap]) - gappedAnnot[ap] = new Annotation("", "", ' ', Float.NaN); - else if (p < resAnnot.length) - gappedAnnot[ap] = resAnnot[p++]; - // is this loop exhaustive of resAnnot? - } - ala.annotations = gappedAnnot; - - AlignmentAnnotation newAnnot = viewport.getAlignment() - .updateFromOrCopyAnnotation(ala); - if (aseq != null) - { - aseq.addAlignmentAnnotation(newAnnot); - newAnnot.adjustForAlignment(); - AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith( - newAnnot, newAnnot.label, newAnnot.getCalcId()); - } - newAnnots.add(newAnnot); - } - - boolean transferFeatures = false; + boolean hasFeatures = false; for (SequenceI sq : job.getInputSequences()) { - if (!sq.getFeatures().hasFeatures() && - (sq.getDBRefs() == null || sq.getDBRefs().size() == 0)) + if (!sq.getFeatures().hasFeatures() + && (sq.getDBRefs() == null || sq.getDBRefs().isEmpty())) continue; - transferFeatures = true; + hasFeatures = true; SequenceI seq = job.seqNames.get(sq.getName()); - SequenceI dseq; - int start = job.start, end = job.end; - boolean[] gapMap = job.gapMap; - ContiguousI seqRange = seq.findPositions(start, end); - while ((dseq = seq).getDatasetSequence() != null) - { - seq = seq.getDatasetSequence(); - } - List sourceRange = new ArrayList<>(); - if (gapMap.length >= end) - { - int lastcol = start, col = start; - do - { - if (col == end || !gapMap[col]) - { - if (lastcol <= (col - 1)) - { - seqRange = seq.findPositions(lastcol, col); - sourceRange.add(seqRange); - } - lastcol = col + 1; - } - } while (col++ < end); - } - else - { - sourceRange.add(seq.findPositions(start, end)); - } - - int i = 0; - int sourceStartEnd[] = new int[sourceRange.size() * 2]; - for (ContiguousI range : sourceRange) - { - sourceStartEnd[i++] = range.getBegin(); - sourceStartEnd[i++] = range.getEnd(); - } - Mapping mp = new Mapping(new MapList( - sourceStartEnd, new int[] - { seq.getStart(), seq.getEnd() }, 1, 1)); - dseq.transferAnnotation(sq, mp); + SequenceI datasetSeq = seq.getRootDatasetSequence(); + List sourceRange = findContiguousRanges(seq, + job.gapMap, job.regionStart, job.regionEnd); + int[] sourceStartEnd = ContiguousI.toStartEndArray(sourceRange); + Mapping mp = new Mapping( + new MapList( + sourceStartEnd, + new int[] { datasetSeq.getStart(), datasetSeq.getEnd() }, + 1, 1)); + datasetSeq.transferAnnotation(sq, mp); } - return new Pair<>(newAnnots, transferFeatures); - } - - @Override - public AnnotationResult getResult() - { - return result; + return new AnnotationResult(returnedAnnot, hasFeatures, featureColours, + featureFilters); } - @Override - public void cancel() + /** + * Updates calcId on provided annotations if not already set. + */ + public void udpateCalcId(Iterable annotations) { - setStatus(JobStatus.CANCELLED); - if (worker != null) + for (var annotation : annotations) { - worker.stop(); + if (annotation.getCalcId() == null + || annotation.getCalcId().isEmpty()) + { + annotation.setCalcId(action.getFullName()); + } + annotation.autoCalculated = action.isAlignmentAnalysis() + && action.getWebService().isInteractive(); } - cancelJobs(); } - public void cancelJobs() + private Annotation[] createGappedAnnotations(Annotation[] annotations, + boolean[] gapMap) { - for (BaseJob job : jobs) + var size = Math.max(annotations.length, gapMap.length); + Annotation[] gappedAnnotations = new Annotation[size]; + for (int p = 0, ap = 0; ap < size; ap++) { - if (!job.isCompleted()) + if (ap < gapMap.length && !gapMap[ap]) + { + gappedAnnotations[ap] = new Annotation("", "", ' ', Float.NaN); + } + else if (p < annotations.length) { - try - { - if (job.getServerJob() != null) - { - client.cancel(job.getServerJob()); - } - job.setStatus(JobStatus.CANCELLED); - } catch (IOException e) - { - Cache.log.error(String.format( - "failed to cancel job %s", job.getServerJob()), e); - } + gappedAnnotations[ap] = annotations[p++]; } } + return gappedAnnotations; } - @Override - public String toString() + // TODO: review ant test!!! + private List findContiguousRanges(SequenceI seq, + boolean[] gapMap, int start, int end) { - var status = taskStatus != null ? taskStatus.name() : "UNSET"; - return String.format("AnnotationTask(%d, %s)", uid, status); + if (gapMap == null || gapMap.length < end) + return List.of(seq.findPositions(start + 1, end + 1)); + List ranges = new ArrayList<>(); + int lastcol = start, col = start; + do + { + if (col == end || !gapMap[col]) + { + if (lastcol < col) + ranges.add(seq.findPositions(lastcol, col)); + lastcol = col + 1; + } + } while (++col <= end); + return ranges; } }