/*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.9.0b2)
- * Copyright (C) 2015 The Jalview Authors
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
*
* This file is part of Jalview.
*
import jalview.analysis.SeqsetUtils;
import jalview.api.AlignViewportI;
import jalview.api.AlignmentViewPanel;
+import jalview.bin.Cache;
import jalview.datamodel.AlignmentAnnotation;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.AnnotatedCollectionI;
import jalview.datamodel.SequenceI;
import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
import jalview.gui.IProgressIndicator;
+import jalview.gui.IProgressIndicatorHandler;
+import jalview.gui.JvOptionPane;
+import jalview.schemes.ResidueProperties;
+import jalview.util.MessageManager;
import jalview.workers.AlignCalcWorker;
+import jalview.ws.api.CancellableI;
+import jalview.ws.api.JobId;
+import jalview.ws.api.SequenceAnnotationServiceI;
+import jalview.ws.api.WSAnnotationCalcManagerI;
+import jalview.ws.gui.AnnotationWsJob;
import jalview.ws.jws2.dm.AAConSettings;
-import jalview.ws.jws2.dm.JabaWsParamSet;
import jalview.ws.jws2.jabaws2.Jws2Instance;
+import jalview.ws.params.ArgumentI;
import jalview.ws.params.WsParamSetI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import compbio.data.sequence.FastaSequence;
-import compbio.metadata.Argument;
-import compbio.metadata.ChunkHolder;
-import compbio.metadata.JobStatus;
-import compbio.metadata.JobSubmissionException;
-import compbio.metadata.Option;
-import compbio.metadata.ResultNotAvailableException;
-
-public abstract class AbstractJabaCalcWorker extends AlignCalcWorker
+public class AbstractJabaCalcWorker extends AlignCalcWorker
+ implements WSAnnotationCalcManagerI
{
protected Jws2Instance service;
protected WsParamSetI preset;
- protected List<Argument> arguments;
+ protected List<ArgumentI> arguments;
protected IProgressIndicator guiProgress;
protected boolean submitGaps = true;
/**
+ * by default, we filter out non-standard residues before submission
+ */
+ protected boolean filterNonStandardResidues = true;
+
+ /**
* Recover any existing parameters for this service
*/
protected void initViewportParams()
{
((jalview.gui.AlignViewport) alignViewport).setCalcIdSettingsFor(
getCalcId(),
- new AAConSettings(true, service, this.preset,
- (arguments != null) ? JabaParamStore
- .getJwsArgsfromJaba(arguments) : null), true);
+ new AAConSettings(true, service, this.preset, arguments),
+ true);
}
}
* @return null or a string used to recover all annotation generated by this
* worker
*/
- public abstract String getCalcId();
+ public String getCalcId()
+ {
+ return service.getAlignAnalysisUI() == null ? null
+ : service.getAlignAnalysisUI().getCalcId();
+ }
public WsParamSetI getPreset()
{
return preset;
}
- public List<Argument> getArguments()
+ public List<ArgumentI> getArguments()
{
return arguments;
}
* @param newarguments
*/
public void updateParameters(final WsParamSetI newpreset,
- final List<Argument> newarguments)
+ final List<ArgumentI> newarguments)
{
preset = newpreset;
arguments = newarguments;
calcMan.startWorker(this);
initViewportParams();
}
-
- public List<Option> getJabaArguments()
- {
- List<Option> newargs = new ArrayList<Option>();
- if (preset != null && preset instanceof JabaWsParamSet)
- {
- newargs.addAll(((JabaWsParamSet) preset).getjabaArguments());
- }
- if (arguments != null && arguments.size() > 0)
- {
- for (Argument rg : arguments)
- {
- if (Option.class.isAssignableFrom(rg.getClass()))
- {
- newargs.add((Option) rg);
- }
- }
- }
- return newargs;
- }
-
protected boolean alignedSeqs = true;
protected boolean nucleotidesAllowed = false;
protected Map<String, SequenceI> seqNames;
+ // TODO: convert to bitset
protected boolean[] gapMap;
int realw;
int end;
+ private AlignFrame alignFrame;
+
+ public boolean[] getGapMap()
+ {
+ return gapMap;
+ }
+
public AbstractJabaCalcWorker(AlignViewportI alignViewport,
AlignmentViewPanel alignPanel)
{
super(alignViewport, alignPanel);
}
- public AbstractJabaCalcWorker(Jws2Instance service,
- AlignFrame alignFrame, WsParamSetI preset, List<Argument> paramset)
+ public AbstractJabaCalcWorker(Jws2Instance service, AlignFrame alignFrame,
+ WsParamSetI preset, List<ArgumentI> paramset)
{
this(alignFrame.getCurrentView(), alignFrame.alignPanel);
+ // TODO: both these fields needed ?
+ this.alignFrame = alignFrame;
this.guiProgress = alignFrame;
this.preset = preset;
this.arguments = paramset;
this.service = service;
+ try
+ {
+ annotService = (jalview.ws.api.SequenceAnnotationServiceI) service
+ .getEndpoint();
+ } catch (ClassCastException cce)
+ {
+ JvOptionPane.showMessageDialog(Desktop.desktop,
+ MessageManager.formatMessage(
+ "label.service_called_is_not_an_annotation_service",
+ new String[]
+ { service.getName() }),
+ MessageManager.getString("label.internal_jalview_error"),
+ JvOptionPane.WARNING_MESSAGE);
+
+ }
+ // configure submission flags
+ if (service.getAlignAnalysisUI() != null)
+ {
+ // instantaneous calculation. Right now that's either AACons or RNAAliFold
+ proteinAllowed = service.getAlignAnalysisUI().isPr();
+ nucleotidesAllowed = service.getAlignAnalysisUI().isNa();
+ alignedSeqs = service.getAlignAnalysisUI().isNeedsAlignedSeqs();
+ bySequence = !service.getAlignAnalysisUI().isAA();
+ filterNonStandardResidues = service.getAlignAnalysisUI()
+ .isFilterSymbols();
+ min_valid_seqs = service.getAlignAnalysisUI().getMinimumSequences();
+ initViewportParams();
+ }
+ else
+ {
+ // assume disorder prediction : per-sequence protein only no gaps
+ // analysis.
+ // TODO - move configuration to UIInfo base class for all these flags !
+ alignedSeqs = false;
+ bySequence = true;
+ filterNonStandardResidues = true;
+ nucleotidesAllowed = false;
+ proteinAllowed = true;
+ submitGaps = false;
+ min_valid_seqs = 1;
+ }
}
/**
*
* @return true if the submission thread should attempt to submit data
*/
- abstract boolean hasService();
+ public boolean hasService()
+ {
+ return annotService != null;
+ }
+
+ protected jalview.ws.api.SequenceAnnotationServiceI annotService = null;
+
+ volatile JobId rslt = null;
- volatile String rslt = "JOB NOT DEFINED";
+ AnnotationWsJob running = null;
+
+ private int min_valid_seqs;
@Override
public void run()
{
return;
}
+
long progressId = -1;
int serverErrorsLeft = 3;
-
+ final boolean cancellable = CancellableI.class
+ .isAssignableFrom(annotService.getClass());
StringBuffer msg = new StringBuffer();
try
{
{
return;
}
- List<compbio.data.sequence.FastaSequence> seqs = getInputSequences(
+ List<SequenceI> seqs = getInputSequences(
alignViewport.getAlignment(),
bySequence ? alignViewport.getSelectionGroup() : null);
- if (seqs == null || !checkValidInputSeqs(true, seqs))
+ if (seqs == null || !checkValidInputSeqs(seqs))
{
+ jalview.bin.Cache.log.debug(
+ "Sequences for analysis service were null or not valid");
calcMan.workerComplete(this);
return;
}
.getAlignmentAnnotation();
if (guiProgress != null)
{
- guiProgress.setProgressBar("JABA " + getServiceActionText(),
+ guiProgress.setProgressBar(service.getActionText(),
progressId = System.currentTimeMillis());
}
- rslt = submitToService(seqs);
+ jalview.bin.Cache.log.debug("submitted " + seqs.size()
+ + " sequences to " + service.getActionText());
+ rslt = annotService.submitToService(seqs, getPreset(),
+ getArguments());
+ if (rslt == null)
+ {
+ return;
+ }
+ // TODO: handle job submission error reporting here.
+
+ // ///
+ // otherwise, construct WsJob and any UI handlers
+ running = new AnnotationWsJob();
+ running.setJobHandle(rslt);
+ running.setSeqNames(seqNames);
+ running.setStartPos(start);
+ running.setSeqs(seqs);
+
+ if (guiProgress != null)
+ {
+ guiProgress.registerHandler(progressId,
+ new IProgressIndicatorHandler()
+ {
+
+ @Override
+ public boolean cancelActivity(long id)
+ {
+ ((CancellableI) annotService).cancel(running);
+ return true;
+ }
+
+ @Override
+ public boolean canCancel()
+ {
+ return cancellable;
+ }
+ });
+ }
+
+ // ///
+ // and poll for updates until job finishes, fails or becomes stale
+
boolean finished = false;
long rpos = 0;
do
{
- JobStatus status = getJobStatus(rslt);
- if (status.equals(JobStatus.FINISHED))
+ Cache.log.debug("Updating status for annotation service.");
+ annotService.updateStatus(running);
+
+ if (running.isFinished())
{
+ Cache.log.debug("Analysis service job reported finished.");
finished = true;
}
- if (calcMan.isPending(this) && isInteractiveUpdate())
+ else
{
- finished = true;
- // cancel this job and yield to the new job
- try
- {
- if (cancelJob(rslt))
- {
- System.err.println("Cancelled AACon job: " + rslt);
- }
- else
- {
- System.err.println("FAILED TO CANCEL AACon job: " + rslt);
- }
-
- } catch (Exception x)
- {
-
- }
- rslt = "CANCELLED JOB";
- return;
+ Cache.log.debug("Status now " + running.getState());
}
- long cpos;
- ChunkHolder stats = null;
- do
+
+ if (calcMan.isPending(this) && isInteractiveUpdate())
{
- cpos = rpos;
- boolean retry = false;
- do
- {
+ Cache.log.debug("Analysis service job is stale. aborting.");
+ // job has become stale.
+ if (!finished) {
+ finished = true;
+ // cancel this job and yield to the new job
try
{
- stats = pullExecStatistics(rslt, rpos);
- } catch (Exception x)
- {
-
- if (x.getMessage().contains(
- "Position in a file could not be negative!"))
+ if (cancellable
+ && ((CancellableI) annotService).cancel(running))
{
- // squash index out of bounds exception- seems to happen for
- // disorder predictors which don't (apparently) produce any
- // progress information and JABA server throws an exception
- // because progress length is -1.
- stats = null;
+ System.err.println("Cancelled AACon job: " + rslt);
}
else
{
- if (--serverErrorsLeft > 0)
- {
- retry = true;
- try
- {
- Thread.sleep(200);
- } catch (InterruptedException q)
- {
- }
- ;
- }
- else
- {
- throw x;
- }
+ System.err.println("FAILED TO CANCEL AACon job: " + rslt);
}
+
+ } catch (Exception x)
+ {
+
}
- } while (retry);
- if (stats != null)
- {
- System.out.print(stats.getChunk());
- msg.append(stats);
- rpos = stats.getNextPosition();
}
- } while (stats != null && rpos > cpos);
+ rslt = running.getJobHandle();
+ return;
+ }
+
+ // pull any stats - some services need to flush log output before
+ // results are available
+ Cache.log.debug("Updating progress log for annotation service.");
- if (!finished && status.equals(JobStatus.FAILED))
+ try
+ {
+ annotService.updateJobProgress(running);
+ } catch (Throwable thr)
+ {
+ Cache.log.debug("Ignoring exception during progress update.",
+ thr);
+ }
+ Cache.log.trace("Result of poll: " + running.getStatus());
+
+ if (!finished && !running.isFailed())
{
try
{
+ Cache.log.debug("Analysis service job thread sleeping.");
Thread.sleep(200);
+ Cache.log.debug("Analysis service job thread woke.");
} catch (InterruptedException x)
{
}
;
}
} while (!finished);
+
+ // TODO: need to poll/retry
if (serverErrorsLeft > 0)
{
try
} catch (InterruptedException x)
{
}
- if (collectAnnotationResultsFor(rslt))
+ }
+ // configure job with the associated view's feature renderer, if one
+ // exists.
+ // TODO: here one would also grab the 'master feature renderer' in order
+ // to enable/disable
+ // features automatically according to user preferences
+ running.setFeatureRenderer(
+ ((jalview.gui.AlignmentPanel) ap).cloneFeatureRenderer());
+ Cache.log.debug("retrieving job results.");
+ List<AlignmentAnnotation> returnedAnnot = annotService
+ .getAlignmentAnnotation(running, this);
+ Cache.log.debug("Obtained " + (returnedAnnot == null ? "no rows"
+ : ("" + returnedAnnot.size())));
+ running.setAnnotation(returnedAnnot);
+
+ if (running.hasResults())
+ {
+ jalview.bin.Cache.log.debug("Updating result annotation from Job "
+ + rslt + " at " + service.getUri());
+ updateResultAnnotation(true);
+ if (running.isTransferSequenceFeatures())
{
- jalview.bin.Cache.log
- .debug("Updating result annotation from Job " + rslt
- + " at " + service.getUri());
- updateResultAnnotation(true);
- ap.adjustAnnotationHeight();
+ jalview.bin.Cache.log.debug(
+ "Updating feature display settings and transferring features from Job "
+ + rslt + " at " + service.getUri());
+ ((jalview.gui.AlignmentPanel) ap)
+ .updateFeatureRendererFrom(running.getFeatureRenderer());
+ // TODO: JAL-1150 - create sequence feature settings API for defining
+ // styles and enabling/disabling feature overlay on alignment panel
+
+ if (alignFrame.alignPanel == ap)
+ {
+ // only do this if the alignFrame is currently showing this view.
+ Desktop.getAlignFrameFor(alignViewport)
+ .setShowSeqFeatures(true);
+ }
}
+ ap.adjustAnnotationHeight();
}
+ Cache.log.debug("Annotation Service Worker thread finished.");
}
-
- catch (JobSubmissionException x)
- {
-
- System.err.println("submission error with " + getServiceActionText()
- + " :");
- x.printStackTrace();
- calcMan.workerCannotRun(this);
- } catch (ResultNotAvailableException x)
- {
- System.err.println("collection error:\nJob ID: " + rslt);
- x.printStackTrace();
- calcMan.workerCannotRun(this);
-
- } catch (OutOfMemoryError error)
- {
- calcMan.workerCannotRun(this);
-
- // consensus = null;
- // hconsensus = null;
- ap.raiseOOMWarning(getServiceActionText(), error);
- } catch (Exception x)
+// TODO: use service specitic exception handlers
+// catch (JobSubmissionException x)
+// {
+//
+// System.err.println(
+// "submission error with " + getServiceActionText() + " :");
+// x.printStackTrace();
+// calcMan.disableWorker(this);
+// } catch (ResultNotAvailableException x)
+// {
+// System.err.println("collection error:\nJob ID: " + rslt);
+// x.printStackTrace();
+// calcMan.disableWorker(this);
+//
+// } catch (OutOfMemoryError error)
+// {
+// calcMan.disableWorker(this);
+//
+// ap.raiseOOMWarning(getServiceActionText(), error);
+// }
+ catch (Throwable x)
{
- calcMan.workerCannotRun(this);
+ calcMan.disableWorker(this);
- // consensus = null;
- // hconsensus = null;
System.err
.println("Blacklisting worker due to unexpected exception:");
x.printStackTrace();
{
guiProgress.setProgressBar("", progressId);
}
- ap.paintAlignment(true);
+ // TODO: may not need to paintAlignment again !
+ ap.paintAlignment(false, false);
}
if (msg.length() > 0)
{
}
/**
- * validate input for dynamic/non-dynamic update context
- *
- * @param dynamic
+ * validate input for dynamic/non-dynamic update context TODO: move to
+ * analysis interface ?
* @param seqs
+ *
* @return true if input is valid
*/
- abstract boolean checkValidInputSeqs(boolean dynamic,
- List<FastaSequence> seqs);
-
- abstract String submitToService(
- List<compbio.data.sequence.FastaSequence> seqs)
- throws JobSubmissionException;
-
- abstract boolean cancelJob(String rslt) throws Exception;
-
- abstract JobStatus getJobStatus(String rslt) throws Exception;
-
- abstract ChunkHolder pullExecStatistics(String rslt, long rpos);
-
- abstract boolean collectAnnotationResultsFor(String rslt)
- throws ResultNotAvailableException;
+ boolean checkValidInputSeqs(List<SequenceI> seqs)
+ {
+ int nvalid = 0;
+ for (SequenceI sq : seqs)
+ {
+ if (sq.getStart() <= sq.getEnd()
+ && (sq.isProtein() ? proteinAllowed : nucleotidesAllowed))
+ {
+ if (submitGaps
+ || sq.getLength() == (sq.getEnd() - sq.getStart() + 1))
+ {
+ nvalid++;
+ }
+ }
+ }
+ return nvalid >= min_valid_seqs;
+ }
public void cancelCurrentJob()
{
try
{
- String id = rslt;
- if (cancelJob(rslt))
+ String id = running.getJobId();
+ if (((CancellableI) annotService).cancel(running))
{
System.err.println("Cancelled job " + id);
}
* @return true if a running job should be cancelled because new input data is
* available for analysis
*/
- abstract boolean isInteractiveUpdate();
+ boolean isInteractiveUpdate()
+ {
+ return service.isInteractiveUpdate();
+ }
- public List<FastaSequence> getInputSequences(AlignmentI alignment,
+ /**
+ * decide what sequences will be analysed TODO: refactor to generate
+ * List<SequenceI> for submission to service interface
+ *
+ * @param alignment
+ * @param inputSeqs
+ * @return
+ */
+ public List<SequenceI> getInputSequences(AlignmentI alignment,
AnnotatedCollectionI inputSeqs)
{
if (alignment == null || alignment.getWidth() <= 0
- || alignment.getSequences() == null || alignment.isNucleotide() ? !nucleotidesAllowed
- : !proteinAllowed)
+ || alignment.getSequences() == null || alignment.isNucleotide()
+ ? !nucleotidesAllowed
+ : !proteinAllowed)
{
return null;
}
inputSeqs = alignment;
}
- List<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
+ List<SequenceI> seqs = new ArrayList<>();
int minlen = 10;
int ln = -1;
if (bySequence)
{
- seqNames = new HashMap<String, SequenceI>();
+ seqNames = new HashMap<>();
}
gapMap = new boolean[0];
start = inputSeqs.getStartRes();
end = inputSeqs.getEndRes();
-
- for (SequenceI sq : ((List<SequenceI>) inputSeqs.getSequences()))
+ // TODO: URGENT! unify with JPred / MSA code to handle hidden regions
+ // correctly
+ // TODO: push attributes into WsJob instance (so they can be safely
+ // persisted/restored
+ for (SequenceI sq : (inputSeqs.getSequences()))
{
- if (bySequence ? sq.findPosition(end + 1)
- - sq.findPosition(start + 1) > minlen - 1 : sq.getEnd()
- - sq.getStart() > minlen - 1)
+ if (bySequence
+ ? sq.findPosition(end + 1)
+ - sq.findPosition(start + 1) > minlen - 1
+ : sq.getEnd() - sq.getStart() > minlen - 1)
{
String newname = SeqsetUtils.unique_name(seqs.size() + 1);
// make new input sequence with or without gaps
{
seqNames.put(newname, sq);
}
- FastaSequence seq;
+ SequenceI seq;
if (submitGaps)
{
- seqs.add(seq = new compbio.data.sequence.FastaSequence(newname,
+ seqs.add(seq = new jalview.datamodel.Sequence(newname,
sq.getSequenceAsString()));
- if (gapMap == null || gapMap.length < seq.getSequence().length())
+ if (gapMap == null || gapMap.length < seq.getLength())
{
boolean[] tg = gapMap;
gapMap = new boolean[seq.getLength()];
}
for (int apos : sq.gapMap())
{
- gapMap[apos] = true; // aligned.
+ char sqc = sq.getCharAt(apos);
+ if (!filterNonStandardResidues
+ || (sq.isProtein() ? ResidueProperties.aaIndex[sqc] < 20
+ : ResidueProperties.nucleotideIndex[sqc] < 5))
+ {
+ gapMap[apos] = true; // aligned and real amino acid residue
+ }
+ ;
}
}
else
{
- seqs.add(seq = new compbio.data.sequence.FastaSequence(newname,
+ // TODO: add ability to exclude hidden regions
+ seqs.add(seq = new jalview.datamodel.Sequence(newname,
AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
sq.getSequenceAsString(start, end + 1))));
+ // for annotation need to also record map to sequence start/end
+ // position in range
+ // then transfer back to original sequence on return.
}
- if (seq.getSequence().length() > ln)
+ if (seq.getLength() > ln)
{
- ln = seq.getSequence().length();
+ ln = seq.getLength();
}
}
}
// acids at each position, and AAcon doesn't gracefully degrade.
for (int p = 0; p < seqs.size(); p++)
{
- FastaSequence sq = seqs.get(p);
- int l = sq.getSequence().length();
+ SequenceI sq = seqs.get(p);
// strip gapped columns
- char[] padded = new char[realw], orig = sq.getSequence()
- .toCharArray();
+ char[] padded = new char[realw],
+ orig = sq.getSequence();
for (int i = 0, pp = 0; i < realw; pp++)
{
if (gapMap[pp])
}
}
}
- seqs.set(p, new compbio.data.sequence.FastaSequence(sq.getId(),
+ seqs.set(p, new jalview.datamodel.Sequence(sq.getName(),
new String(padded)));
}
}
updateResultAnnotation(false);
}
- public abstract void updateResultAnnotation(boolean immediate);
-
- public abstract String getServiceActionText();
+ public void updateResultAnnotation(boolean immediate)
+ {
+ if ((immediate || !calcMan.isWorking(this)) && running != null
+ && running.hasResults())
+ {
+ List<AlignmentAnnotation> ourAnnot = running.getAnnotation();
+ updateOurAnnots(ourAnnot);
+ }
+ }
/**
* notify manager that we have started, and wait for a free calculation slot
protected boolean checkDone()
{
calcMan.notifyStart(this);
- ap.paintAlignment(false);
+ ap.paintAlignment(false, false);
while (!calcMan.notifyWorking(this))
{
if (calcMan.isWorking(this))
{
if (ap != null)
{
- ap.paintAlignment(false);
+ ap.paintAlignment(false, false);
}
Thread.sleep(200);
}
}
our.clear();
-
+ // validate rows and update Alignmment state
+ for (AlignmentAnnotation an : ourAnnots)
+ {
+ alignViewport.getAlignment().validateAnnotation(an);
+ }
+ // TODO: may need a menu refresh after this
+ // af.setMenusForViewport();
ap.adjustAnnotationHeight();
}
}
+ public SequenceAnnotationServiceI getService()
+ {
+ return annotService;
+ }
+
}