/* * 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.gui; import jalview.bin.Cache; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; import jalview.datamodel.AlignmentOrder; import jalview.datamodel.AlignmentView; import jalview.datamodel.HiddenColumns; import jalview.datamodel.SequenceI; import jalview.gui.AlignFrame; import jalview.gui.Desktop; import jalview.gui.SplitFrame; import jalview.gui.WebserviceInfo; import jalview.util.MessageManager; import jalview.ws.AWSThread; import jalview.ws.AWsJob; import jalview.ws.JobStateSummary; import jalview.ws.WSClientI; import jalview.ws.api.CancellableI; import jalview.ws.api.JobId; import jalview.ws.api.MultipleSequenceAlignmentI; import jalview.ws.gui.WsJob.JobState; import jalview.ws.params.ArgumentI; import jalview.ws.params.WsParamSetI; import java.util.ArrayList; import java.util.List; import javax.swing.JInternalFrame; public class MsaWSThread extends AWSThread implements WSClientI { boolean submitGaps = false; // pass sequences including gaps to alignment // service boolean preserveOrder = true; // and always store and recover sequence // order String alTitle; // name which will be used to form new alignment window. AlignmentI dataset; // dataset to which the new alignment will be // associated. MultipleSequenceAlignmentI server = null; /** * set basic options for this (group) of Msa jobs * * @param subgaps * boolean * @param presorder * boolean */ private MsaWSThread(MultipleSequenceAlignmentI server, String wsUrl, WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame, AlignmentView alview, String wsname, boolean subgaps, boolean presorder) { super(alFrame, wsinfo, alview, wsname, wsUrl); this.server = server; this.submitGaps = subgaps; this.preserveOrder = presorder; } /** * create one or more Msa jobs to align visible sequences in _msa * * @param title * String * @param _msa * AlignmentView * @param subgaps * boolean * @param presorder * boolean * @param seqset * Alignment */ public MsaWSThread(MultipleSequenceAlignmentI server2, WsParamSetI preset, List paramset, String wsUrl, WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame, String wsname, String title, AlignmentView _msa, boolean subgaps, boolean presorder, AlignmentI seqset) { this(server2, wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder); OutputHeader = wsInfo.getProgressText(); alTitle = title; dataset = seqset; SequenceI[][] conmsa = _msa.getVisibleContigs('-'); if (conmsa != null) { int nvalid = 0, njobs = conmsa.length; jobs = new AWsJob[njobs]; for (int j = 0; j < njobs; j++) { if (j != 0) { jobs[j] = new MsaWSJob(this, wsinfo.addJobPane(), conmsa[j]); } else { jobs[j] = new MsaWSJob(this, 0, conmsa[j]); } if (jobs[j].hasValidInput()) { nvalid++; } jobs[j].setPreset(preset); jobs[j].setArguments(paramset); ((MsaWSJob) jobs[j]).alignmentProgram = wsname; if (njobs > 0) { wsinfo.setProgressName("region " + jobs[j].getJobnum(), jobs[j].getJobnum()); } wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader); } validInput = nvalid > 0; } } boolean validInput = false; /** * * @return true if the thread will perform a calculation */ public boolean hasValidInput() { return validInput; } @Override public boolean isCancellable() { return server instanceof CancellableI; } @Override public void cancelJob() { // TODO decide if when some jobs are not cancellable to shut down the thread // anyhow ? 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 { CancellableI service = (CancellableI) server; boolean cancelledJob = service.cancel((WsJob) jobs[job]); if (cancelledJob) { // CANCELLED_JOB cancelledMessage = "Job cancelled."; ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this // ugliness - wsInfo.setStatus(jobs[job].getJobnum(), WebserviceInfo.STATE_CANCELLED_OK); } else { // VALID UNSTOPPABLE JOB cancelledMessage += "Server cannot cancel this job. just close the window.\n"; cancelled = false; // wsInfo.setStatus(jobs[job].jobnum, // WebserviceInfo.STATE_RUNNING); } } 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"); } else { // if we hadn't submitted then just mark the job as cancelled. jobs[job].setSubjobComplete(true); wsInfo.setStatus(jobs[job].getJobnum(), WebserviceInfo.STATE_CANCELLED_OK); } } 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 { // TODO: investigate if we still need to cast here in J1.6 MsaWSJob j = ((MsaWSJob) job); // this is standard code, but since the interface doesn't comprise of a // basic one that implements (getJobStatus, pullExecStatistics) we have to // repeat the code for all jw2s services. server.updateStatus(j); server.updateJobProgress(j); } @Override public void StartJob(AWsJob job) { Exception lex = null; // boiler plate template if (!(job instanceof MsaWSJob)) { throw new Error(MessageManager.formatMessage( "error.implementation_error_msawbjob_called", new String[] { job.getClass().toString() })); } MsaWSJob j = (MsaWSJob) job; if (j.isSubmitted()) { if (Cache.log.isDebugEnabled()) { Cache.log.debug( "Tried to submit an already submitted job " + j.getJobId()); } return; } // end boilerplate if (j.seqs == null || j.seqs.size() == 0) { // special case - selection consisted entirely of empty sequences... j.setState(JobState.FINISHED); j.setStatus(MessageManager.getString("label.empty_alignment_job")); } try { j.addInitialStatus(); // list the presets/parameters used for the job in // status try { JobId jobHandle = server.align(j.seqs, j.getPreset(), j.getArguments()); if (jobHandle != null) { j.setJobHandle(jobHandle); } } catch (Throwable throwable) { Cache.log.error("failed to send the job to the alignment server", throwable); if (!server.handleSubmitError(throwable, j, wsInfo)) { if (throwable instanceof Exception) { throw ((Exception) throwable); } if (throwable instanceof Error) { throw ((Error) throwable); } } } ///// generic if (j.getJobId() != null) { j.setSubmitted(true); j.setSubjobComplete(false); // System.out.println(WsURL + " Job Id '" + jobId + "'"); return; } else { throw new Exception(MessageManager.formatMessage( "exception.web_service_returned_null_try_later", new String[] { WsUrl })); } } //// jabaws specific //// generic catch (Error e) { // 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.printStackTrace(System.err); wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR); wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_SERVERERROR); } catch (Exception e) { // 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.printStackTrace(System.err); wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR); wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_SERVERERROR); } finally { if (!j.isSubmitted()) { // Boilerplate code here // TODO: JBPNote catch timeout or other fault types explicitly j.setAllowedServerExceptions(0); wsInfo.appendProgressText(j.getJobnum(), MessageManager.getString( "info.failed_to_submit_sequences_for_alignment")); } } } @Override public void parseResult() { long progbar = System.currentTimeMillis(); wsInfo.setProgressBar( MessageManager.getString("status.collecting_job_results"), progbar); int results = 0; // number of result sets received JobStateSummary finalState = new JobStateSummary(); try { for (int j = 0; j < jobs.length; j++) { MsaWSJob msjob = ((MsaWSJob) jobs[j]); if (jobs[j].isFinished() && msjob.alignment == null) { int nunchanged = 3, nexcept = 3; boolean jpchanged = false, jpex = false; do { try { jpchanged = server.updateJobProgress(msjob); jpex = false; if (jpchanged) { nexcept = 3; } } catch (Exception e) { Cache.log.warn( "Exception when retrieving remaining Job progress data for job " + msjob.getJobId() + " on server " + WsUrl); e.printStackTrace(); nexcept--; nunchanged = 3; // set flag remember that we've had an exception. jpex = true; jpchanged = false; } if (!jpchanged) { try { Thread.sleep(jpex ? 2400 : 1200); // wait a bit longer if we // experienced an exception. } catch (Exception ex) { } ; nunchanged--; } } while (nunchanged > 0 && nexcept > 0); if (Cache.log.isDebugEnabled()) { System.out.println("Job Execution file for job: " + msjob.getJobId() + " on server " + WsUrl); System.out.println(msjob.getStatus()); System.out.println("*** End of status"); } ///// jabaws specific(ish) Get Result from Server when available try { msjob.alignment = server.getAlignmentFor(msjob.getJobHandle()); } catch (Exception e) { if (!server.handleCollectionException(e, msjob, wsInfo)) { Cache.log.error("Couldn't get Alignment for job.", e); // TODO: Increment count and retry ? msjob.setState(JobState.SERVERERROR); } } } finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]); if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete() && jobs[j].hasResults()) { results++; } } } 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(); } updateGlobalStatus(finalState); wsInfo.setProgressBar(null, progbar); } /** * Display alignment results in a new frame (or - not currently supported - * added to an existing alignment). * * @param newFrame */ void displayResults(boolean newFrame) { // view input or result data for each block List alorders = new ArrayList<>(); SequenceI[][] results = new SequenceI[jobs.length][]; AlignmentOrder[] orders = new AlignmentOrder[jobs.length]; String lastProgram = null; MsaWSJob msjob; for (int j = 0; j < jobs.length; j++) { if (jobs[j].hasResults()) { msjob = (MsaWSJob) jobs[j]; Object[] res = msjob.getAlignment(); lastProgram = msjob.getAlignmentProgram(); alorders.add((AlignmentOrder) res[1]); results[j] = (SequenceI[]) res[0]; orders[j] = (AlignmentOrder) res[1]; // SequenceI[] alignment = input.getUpdated } else { results[j] = null; } } 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]; HiddenColumns hidden = (HiddenColumns) newview[1]; Alignment al = new Alignment(alignment); // TODO: add 'provenance' property to alignment from the method notes if (lastProgram != null) { al.setProperty("Alignment Program", lastProgram); } // accompanying each subjob if (dataset != null) { al.setDataset(dataset); } propagateDatasetMappings(al); // JBNote- TODO: warn user if a block is input rather than aligned data ? if (newFrame) { displayInNewFrame(al, alorders, hidden); } else { // TODO 2.9.x feature System.out.println("MERGE WITH OLD FRAME"); // TODO: modify alignment in original frame, replacing old for new // alignment using the commands.EditCommand model to ensure the update can // be undone } } /** * Display the alignment result in a new frame. * * @param al * @param alorders * @param columnselection */ protected void displayInNewFrame(AlignmentI al, List alorders, HiddenColumns hidden) { AlignFrame af = new AlignFrame(al, hidden, AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT); // initialise with same renderer settings as in parent alignframe. af.getFeatureRenderer().transferSettings(this.featureSettings); if (alorders.size() > 0) { addSortByMenuItems(af, alorders); } // TODO: refactor retrieve and show as new splitFrame as Desktop method /* * If alignment was requested from one half of a SplitFrame, show in a * SplitFrame with the other pane similarly aligned. */ AlignFrame requestedBy = getRequestingAlignFrame(); if (requestedBy != null && requestedBy.getSplitViewContainer() != null && requestedBy.getSplitViewContainer() .getComplement(requestedBy) != null) { AlignmentI complement = requestedBy.getSplitViewContainer() .getComplement(requestedBy); String complementTitle = requestedBy.getSplitViewContainer() .getComplementTitle(requestedBy); // becomes null if the alignment window was closed before the alignment // job finished. AlignmentI copyComplement = new Alignment(complement); // todo should this be done by copy constructor? copyComplement.setGapCharacter(complement.getGapCharacter()); // share the same dataset (and the mappings it holds) copyComplement.setDataset(complement.getDataset()); copyComplement.alignAs(al); if (copyComplement.getHeight() > 0) { af.setTitle(alTitle); AlignFrame af2 = new AlignFrame(copyComplement, AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT); af2.setTitle(complementTitle); String linkedTitle = MessageManager .getString("label.linked_view_title"); JInternalFrame splitFrame = new SplitFrame( al.isNucleotide() ? af : af2, al.isNucleotide() ? af2 : af); Desktop.addInternalFrame(splitFrame, linkedTitle, -1, -1); return; } } /* * Not from SplitFrame, or failed to created a complementary alignment */ Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT); } /** * Add sort order options to the AlignFrame menus. * * @param af * @param alorders */ protected void addSortByMenuItems(AlignFrame af, List alorders) { // update orders if (alorders.size() == 1) { af.addSortByOrderMenuItem(WebServiceName + " Ordering", alorders.get(0)); } else { // construct a non-redundant ordering set List names = new ArrayList<>(); for (int i = 0, l = alorders.size(); i < l; i++) { String orderName = " Region " + i; int j = i + 1; while (j < l) { if (alorders.get(i).equals(alorders.get(j))) { alorders.remove(j); l--; orderName += "," + j; } else { j++; } } if (i == 0 && j == 1) { names.add(""); } else { names.add(orderName); } } for (int i = 0, l = alorders.size(); i < l; i++) { af.addSortByOrderMenuItem( WebServiceName + (names.get(i)) + " Ordering", alorders.get(i)); } } } @Override public boolean canMergeResults() { return false; } }