/*
* Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)
* Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package jalview.ws;
import java.util.*;
import jalview.analysis.*;
import jalview.bin.*;
import jalview.datamodel.*;
import jalview.gui.*;
import jalview.io.NewickFile;
import vamsas.objects.simple.MsaResult;
import vamsas.objects.simple.SeqSearchResult;
/**
*
* Title:
*
*
*
* Description:
*
*
*
* Copyright: Copyright (c) 2004
*
*
*
* Company: Dundee University
*
*
* @author not attributable
* @version 1.0
*/
class SeqSearchWSThread extends WSThread implements WSClientI
{
String dbs = null;
boolean profile = false;
class SeqSearchWSJob extends WSThread.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("Job never ran - input returned to user.");
}
}
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(
"Implementation error: minlen must be zero or more.");
}
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.
*/
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, Hashtable 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].jobnum,
jobs[j].jobnum);
}
wsinfo.setProgressText(jobs[j].jobnum, OutputHeader);
}
}
}
public boolean isCancellable()
{
return true;
}
public void cancelJob()
{
if (!jobComplete && jobs != null)
{
boolean cancelled = true;
for (int job = 0; job < jobs.length; job++)
{
if (jobs[job].submitted && !jobs[job].subjobComplete)
{
String cancelledMessage = "";
try
{
vamsas.objects.simple.WsJobId cancelledJob = server
.cancel(jobs[job].jobId);
if (cancelledJob.getStatus() == 2)
{
// CANCELLED_JOB
cancelledMessage = "Job cancelled.";
((SeqSearchWSJob) jobs[job]).cancel();
wsInfo.setStatus(jobs[job].jobnum,
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].jobId, exc);
}
wsInfo.setProgressText(jobs[job].jobnum, 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");
}
}
}
void pollJob(WSJob job) throws Exception
{
((SeqSearchWSJob) job).result = server
.getResult(((SeqSearchWSJob) job).jobId);
}
void StartJob(WSJob job)
{
if (!(job instanceof SeqSearchWSJob))
{
throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
+ job.getClass());
}
SeqSearchWSJob j = (SeqSearchWSJob) job;
if (j.submitted)
{
if (Cache.log.isDebugEnabled())
{
Cache.log.debug("Tried to submit an already submitted job "
+ j.jobId);
}
return;
}
if (j.seqs.getSeqs() == null)
{
// special case - selection consisted entirely of empty sequences...
j.submitted = true;
j.result = new MsaResult();
j.result.setFinished(true);
j.result.setStatus("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.jobId = jobsubmit.getJobId();
j.submitted = true;
j.subjobComplete = false;
// System.out.println(WsURL + " Job Id '" + jobId + "'");
}
else
{
if (jobsubmit == null)
{
throw new Exception(
"Server at "
+ WsUrl
+ " returned null object, it probably cannot be contacted. Try again later ?");
}
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.allowedServerExceptions = 0;
wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_STOPPED_SERVERERROR);
wsInfo
.appendProgressText(
j.jobnum,
"Failed to submit sequences for alignment.\n"
+ "It is most likely that there is a problem with the server.\n"
+ "Just close the window\n");
// 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;
}
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].submitted && jobs[j].subjobComplete
&& jobs[j].hasResults())
{
results++;
vamsas.objects.simple.Alignment valign = ((SeqSearchResult) jobs[j].result)
.getAlignment();
if (valign != null)
{
wsInfo.appendProgressText(jobs[j].jobnum,
"\nAlignment Object Method Notes\n");
String[] lines = valign.getMethod();
for (int line = 0; line < lines.length; line++)
{
wsInfo.appendProgressText(jobs[j].jobnum, 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()
{
public void actionPerformed(java.awt.event.ActionEvent evt)
{
displayResults(true);
}
});
wsInfo.mergeResults
.addActionListener(new java.awt.event.ActionListener()
{
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++)
{
Hashtable featureColours = new Hashtable();
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, "Tree from " + 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);
}
}
public boolean canMergeResults()
{
return false;
}
}