/*
* Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
* Copyright (C) $$Year-Rel$$ The Jalview Authors
*
* This file is part of Jalview.
*
* Jalview 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.
*
* Jalview 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 Jalview. If not, see .
* The Jalview Authors are detailed in the 'AUTHORS' file.
*/
package jalview.ws.jws1;
import jalview.analysis.AlignSeq;
import jalview.api.FeatureColourI;
import jalview.bin.Cache;
import jalview.datamodel.Alignment;
import jalview.datamodel.AlignmentView;
import jalview.datamodel.SequenceI;
import jalview.gui.AlignFrame;
import jalview.gui.Desktop;
import jalview.gui.WebserviceInfo;
import jalview.io.NewickFile;
import jalview.util.MessageManager;
import jalview.ws.AWsJob;
import jalview.ws.JobStateSummary;
import jalview.ws.WSClientI;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Vector;
import vamsas.objects.simple.MsaResult;
import vamsas.objects.simple.SeqSearchResult;
class SeqSearchWSThread extends JWS1Thread implements WSClientI
{
String dbs = null;
boolean profile = false;
class SeqSearchWSJob extends WSJob
{
// hold special input for this
vamsas.objects.simple.SequenceSet seqs = new vamsas.objects.simple.SequenceSet();
/**
* MsaWSJob
*
* @param jobNum
* int
* @param jobId
* String
*/
public SeqSearchWSJob(int jobNum, SequenceI[] inSeqs)
{
this.jobnum = jobNum;
if (!prepareInput(inSeqs, 2))
{
submitted = true;
subjobComplete = true;
result = new MsaResult();
result.setFinished(true);
result.setStatus(MessageManager.getString("label.job_never_ran"));
}
}
Hashtable SeqNames = new Hashtable();
Vector emptySeqs = new Vector();
/**
* prepare input sequences for service
*
* @param seqs
* jalview sequences to be prepared
* @param minlen
* minimum number of residues required for this MsaWS service
* @return true if seqs contains sequences to be submitted to service.
*/
private boolean prepareInput(SequenceI[] seqs, int minlen)
{
int nseqs = 0;
if (minlen < 0)
{
throw new Error(
MessageManager
.getString("error.implementation_error_minlen_must_be_greater_zero"));
}
for (int i = 0; i < seqs.length; i++)
{
if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
{
nseqs++;
}
}
boolean valid = nseqs >= 1; // need at least one sequence for valid input
// TODO: generalise
vamsas.objects.simple.Sequence[] seqarray = (valid) ? new vamsas.objects.simple.Sequence[nseqs]
: null;
boolean submitGaps = (nseqs == 1) ? false : true; // profile is submitted
// with gaps
for (int i = 0, n = 0; i < seqs.length; i++)
{
String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
// for
// any
// subjob
SeqNames.put(newname,
jalview.analysis.SeqsetUtils.SeqCharacterHash(seqs[i]));
if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
{
seqarray[n] = new vamsas.objects.simple.Sequence();
seqarray[n].setId(newname);
seqarray[n++].setSeq((submitGaps) ? seqs[i].getSequenceAsString()
: AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
seqs[i].getSequenceAsString()));
}
else
{
String empty = null;
if (seqs[i].getEnd() >= seqs[i].getStart())
{
empty = (submitGaps) ? seqs[i].getSequenceAsString() : AlignSeq
.extractGaps(jalview.util.Comparison.GapChars,
seqs[i].getSequenceAsString());
}
emptySeqs.add(new String[] { newname, empty });
}
}
if (submitGaps)
{
// almost certainly have to remove gapped columns here
}
this.seqs = new vamsas.objects.simple.SequenceSet();
this.seqs.setSeqs(seqarray);
return valid;
}
/**
*
* @return true if getAlignment will return a valid alignment result.
*/
@Override
public boolean hasResults()
{
if (subjobComplete
&& result != null
&& result.isFinished()
&& ((SeqSearchResult) result).getAlignment() != null
&& ((SeqSearchResult) result).getAlignment().getSeqs() != null)
{
return true;
}
return false;
}
/**
* return sequence search results for display
*
* @return null or { Alignment(+features and annotation), NewickFile)}
*/
public Object[] getAlignment(Alignment dataset,
Map featureColours)
{
if (result != null && result.isFinished())
{
SequenceI[] alseqs = null;
// char alseq_gapchar = '-';
// int alseq_l = 0;
if (((SeqSearchResult) result).getAlignment() != null)
{
alseqs = getVamsasAlignment(((SeqSearchResult) result)
.getAlignment());
// alseq_gapchar = ( (SeqSearchResult)
// result).getAlignment().getGapchar().charAt(0);
// alseq_l = alseqs.length;
}
/**
* what has to be done. 1 - annotate returned alignment with annotation
* file and sequence features file, and associate any tree-nodes. 2.
* connect alignment back to any associated dataset: 2.a. deuniquify
* recovers sequence information - but additionally, relocations must be
* made from the returned aligned sequence back to the dataset.
*/
// construct annotated alignment as it would be done by the jalview
// applet
jalview.datamodel.Alignment al = new Alignment(alseqs);
// al.setDataset(dataset);
// make dataset
String inFile = null;
try
{
inFile = ((SeqSearchResult) result).getAnnotation();
if (inFile != null && inFile.length() > 0)
{
new jalview.io.AnnotationFile().readAnnotationFile(al, inFile,
jalview.io.AppletFormatAdapter.PASTE);
}
} catch (Exception e)
{
System.err
.println("Failed to parse the annotation file associated with the alignment.");
System.err.println(">>>EOF" + inFile + "\n<< 0)
{
jalview.io.FeaturesFile ff = new jalview.io.FeaturesFile(
inFile, jalview.io.AppletFormatAdapter.PASTE);
ff.parse(al, featureColours, false);
}
} catch (Exception e)
{
System.err
.println("Failed to parse the Features file associated with the alignment.");
System.err.println(">>>EOF" + inFile + "\n<< 0)
{
nf = new jalview.io.NewickFile(inFile,
jalview.io.AppletFormatAdapter.PASTE);
if (!nf.isValid())
{
nf.close();
nf = null;
}
}
} catch (Exception e)
{
System.err
.println("Failed to parse the treeFile associated with the alignment.");
System.err.println(">>>EOF" + inFile + "\n<< 0)
{
wsinfo.setProgressName("region " + jobs[j].getJobnum(),
jobs[j].getJobnum());
}
wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
}
}
}
@Override
public boolean isCancellable()
{
return true;
}
@Override
public void cancelJob()
{
if (!jobComplete && jobs != null)
{
boolean cancelled = true;
for (int job = 0; job < jobs.length; job++)
{
if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
{
String cancelledMessage = "";
try
{
vamsas.objects.simple.WsJobId cancelledJob = server
.cancel(jobs[job].getJobId());
if (cancelledJob.getStatus() == 2)
{
// CANCELLED_JOB
cancelledMessage = "Job cancelled.";
((SeqSearchWSJob) jobs[job]).cancel();
wsInfo.setStatus(jobs[job].getJobnum(),
WebserviceInfo.STATE_CANCELLED_OK);
}
else if (cancelledJob.getStatus() == 3)
{
// VALID UNSTOPPABLE JOB
cancelledMessage += "Server cannot cancel this job. just close the window.\n";
cancelled = false;
// wsInfo.setStatus(jobs[job].jobnum,
// WebserviceInfo.STATE_RUNNING);
}
if (cancelledJob.getJobId() != null)
{
cancelledMessage += ("[" + cancelledJob.getJobId() + "]");
}
cancelledMessage += "\n";
} catch (Exception exc)
{
cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
+ exc + "\n");
Cache.log.warn(
"Exception whilst cancelling " + jobs[job].getJobId(),
exc);
}
wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
+ cancelledMessage + "\n");
}
}
if (cancelled)
{
wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
jobComplete = true;
}
this.interrupt(); // kick thread to update job states.
}
else
{
if (!jobComplete)
{
wsInfo.setProgressText(OutputHeader
+ "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
}
}
}
@Override
public void pollJob(AWsJob job) throws Exception
{
((SeqSearchWSJob) job).result = server.getResult(((SeqSearchWSJob) job)
.getJobId());
}
@Override
public void StartJob(AWsJob job)
{
if (!(job instanceof SeqSearchWSJob))
{
throw new Error(MessageManager.formatMessage(
"error.implementation_error_msawbjob_called",
new String[] { job.getClass().toString() }));
}
SeqSearchWSJob j = (SeqSearchWSJob) job;
if (j.isSubmitted())
{
if (Cache.log.isDebugEnabled())
{
Cache.log.debug("Tried to submit an already submitted job "
+ j.getJobId());
}
return;
}
if (j.seqs.getSeqs() == null)
{
// special case - selection consisted entirely of empty sequences...
j.setSubmitted(true);
j.result = new MsaResult();
j.result.setFinished(true);
j.result.setStatus(MessageManager
.getString("label.empty_alignment_job"));
((MsaResult) j.result).setMsa(null);
}
try
{
vamsas.objects.simple.WsJobId jobsubmit = server.search(
j.seqs.getSeqs()[0], dbArg);
if ((jobsubmit != null) && (jobsubmit.getStatus() == 1))
{
j.setJobId(jobsubmit.getJobId());
j.setSubmitted(true);
j.setSubjobComplete(false);
// System.out.println(WsURL + " Job Id '" + jobId + "'");
}
else
{
if (jobsubmit == null)
{
throw new Exception(MessageManager.formatMessage(
"exception.web_service_returned_null_try_later",
new String[] { WsUrl }));
}
throw new Exception(jobsubmit.getJobId());
}
} catch (Exception e)
{
// TODO: JBPNote catch timeout or other fault types explicitly
// For unexpected errors
System.err
.println(WebServiceName
+ "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
+ "When contacting Server:" + WsUrl + "\n"
+ e.toString() + "\n");
j.setAllowedServerExceptions(0);
wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
wsInfo.setStatus(j.getJobnum(),
WebserviceInfo.STATE_STOPPED_SERVERERROR);
wsInfo.appendProgressText(j.getJobnum(), MessageManager
.getString("info.failed_to_submit_sequences_for_alignment"));
// e.printStackTrace(); // TODO: JBPNote DEBUG
}
}
private jalview.datamodel.Sequence[] getVamsasAlignment(
vamsas.objects.simple.Alignment valign)
{
vamsas.objects.simple.Sequence[] seqs = valign.getSeqs().getSeqs();
jalview.datamodel.Sequence[] msa = new jalview.datamodel.Sequence[seqs.length];
for (int i = 0, j = seqs.length; i < j; i++)
{
msa[i] = new jalview.datamodel.Sequence(seqs[i].getId(),
seqs[i].getSeq());
}
return msa;
}
@Override
public void parseResult()
{
int results = 0; // number of result sets received
JobStateSummary finalState = new JobStateSummary();
try
{
for (int j = 0; j < jobs.length; j++)
{
finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
&& jobs[j].hasResults())
{
results++;
vamsas.objects.simple.Alignment valign = ((SeqSearchResult) ((SeqSearchWSJob) jobs[j]).result)
.getAlignment();
if (valign != null)
{
wsInfo.appendProgressText(jobs[j].getJobnum(), MessageManager
.getString("info.alignment_object_method_notes"));
String[] lines = valign.getMethod();
for (int line = 0; line < lines.length; line++)
{
wsInfo.appendProgressText(jobs[j].getJobnum(), lines[line]
+ "\n");
}
// JBPNote The returned files from a webservice could be
// hidden behind icons in the monitor window that,
// when clicked, pop up their corresponding data
}
}
}
} catch (Exception ex)
{
Cache.log.error("Unexpected exception when processing results for "
+ alTitle, ex);
wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
}
if (results > 0)
{
wsInfo.showResultsNewFrame
.addActionListener(new java.awt.event.ActionListener()
{
@Override
public void actionPerformed(java.awt.event.ActionEvent evt)
{
displayResults(true);
}
});
wsInfo.mergeResults
.addActionListener(new java.awt.event.ActionListener()
{
@Override
public void actionPerformed(java.awt.event.ActionEvent evt)
{
displayResults(false);
}
});
wsInfo.setResultsReady();
}
else
{
wsInfo.setFinishedNoResults();
}
}
void displayResults(boolean newFrame)
{
if (!newFrame)
{
System.err.println("MERGE WITH OLD FRAME NOT IMPLEMENTED");
return;
}
// each subjob is an independent alignment for the moment
// Alignment al[] = new Alignment[jobs.length];
// NewickFile nf[] = new NewickFile[jobs.length];
for (int j = 0; j < jobs.length; j++)
{
Map featureColours = new HashMap();
Alignment al = null;
NewickFile nf = null;
if (jobs[j].hasResults())
{
Object[] res = ((SeqSearchWSJob) jobs[j]).getAlignment(dataset,
featureColours);
if (res == null)
{
continue;
}
;
al = (Alignment) res[0];
nf = (NewickFile) res[1];
}
else
{
al = null;
nf = null;
continue;
}
/*
* We can't map new alignment back with insertions from input's hidden
* regions until dataset mapping is sorted out... but basically it goes
* like this: 1. Merge each domain hit back onto the visible segments in
* the same way as a Jnet prediction is mapped back
*
* Object[] newview = input.getUpdatedView(results, orders, getGapChar());
* // trash references to original result data for (int j = 0; j <
* jobs.length; j++) { results[j] = null; orders[j] = null; } SequenceI[]
* alignment = (SequenceI[]) newview[0]; ColumnSelection columnselection =
* (ColumnSelection) newview[1]; Alignment al = new Alignment(alignment);
*
* if (dataset != null) { al.setDataset(dataset); }
*
* propagateDatasetMappings(al); }
*/
AlignFrame af = new AlignFrame(al,// columnselection,
AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
if (nf != null)
{
af.ShowNewickTree(nf, MessageManager.formatMessage(
"label.tree_from", new String[] { this.alTitle }));
}
// initialise with same renderer settings as in parent alignframe.
af.getFeatureRenderer().transferSettings(this.featureSettings);
Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
AlignFrame.DEFAULT_HEIGHT);
}
}
@Override
public boolean canMergeResults()
{
return false;
}
}