JAL-3954 Prepare a skeleton for PHMMER web service.
authorMateusz Warowny <mmzwarowny@dundee.ac.uk>
Tue, 8 Feb 2022 19:59:02 +0000 (20:59 +0100)
committerMateusz Warowny <mmzwarowny@dundee.ac.uk>
Tue, 8 Feb 2022 19:59:02 +0000 (20:59 +0100)
src/jalview/ws2/gui/PhmmerMenuBuilder.java [new file with mode: 0644]
src/jalview/ws2/hmmer/ParamSetFactory.java [new file with mode: 0644]
src/jalview/ws2/hmmer/PhmmerParamDatastore.java [new file with mode: 0644]
src/jalview/ws2/hmmer/PhmmerWSDiscoverer.java [new file with mode: 0644]
src/jalview/ws2/hmmer/PhmmerWebService.java [new file with mode: 0644]
src/jalview/ws2/operations/PhmmerOperation.java [new file with mode: 0644]
src/jalview/ws2/operations/PhmmerWorker.java [new file with mode: 0644]

diff --git a/src/jalview/ws2/gui/PhmmerMenuBuilder.java b/src/jalview/ws2/gui/PhmmerMenuBuilder.java
new file mode 100644 (file)
index 0000000..09e549e
--- /dev/null
@@ -0,0 +1,77 @@
+package jalview.ws2.gui;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.AlignViewport;
+import jalview.util.MessageManager;
+import jalview.ws.params.ArgumentI;
+import jalview.ws2.PollingTaskExecutor;
+import jalview.ws2.operations.PhmmerOperation;
+import jalview.ws2.operations.PhmmerWorker;
+
+public class PhmmerMenuBuilder implements MenuEntryProviderI
+{
+  PhmmerOperation operation;
+
+  public PhmmerMenuBuilder(PhmmerOperation operation)
+  {
+    this.operation = operation;
+  }
+
+  @Override
+  public void buildMenu(JMenu parent, AlignFrame frame)
+  {
+    {
+      var item = new JMenuItem(MessageManager.formatMessage(
+          "label.calcname_with_default_settings", operation.getName()));
+      item.addActionListener((event) -> {
+        // TODO: not sure if the right way to get sequences for phmmer
+        final AlignmentView msa = frame.gatherSequencesForAlignment();
+        if (msa != null)
+        {
+          startWorker(frame, msa, Collections.emptyList());
+        }
+      });
+      parent.add(item);
+    }
+    if (operation.hasParameters())
+    {
+      var item = new JMenuItem(MessageManager.getString("label.edit_settings_and_run"));
+      item.setToolTipText(MessageManager.getString("label.view_and_change_parameters_before_running_calculation"));
+      item.addActionListener((event) -> {
+        final AlignmentView msa = frame.gatherSequencesForAlignment();
+        if (msa != null)
+        {
+          MenuEntryProviderI.openEditParamsDialog(operation.getParamStore(),
+              null, null)
+              .thenAcceptAsync((arguments) -> {
+                if (arguments != null)
+                {
+                  startWorker(frame, msa, arguments);
+                }
+              });
+        }
+      });
+    }
+    SequenceI[] sequences = frame.getViewport().getAlignment().getSequencesArray();
+  }
+
+  private void startWorker(AlignFrame frame, AlignmentView msa,
+      List<ArgumentI> arguments)
+  {
+    final AlignViewport viewport = frame.getViewport();
+    final PollingTaskExecutor executor = viewport.getWSExecutor();
+    if (msa != null) 
+    {
+      PhmmerWorker worker = new PhmmerWorker(operation, msa, arguments);
+      executor.submit(worker);
+    }
+  }
+}
diff --git a/src/jalview/ws2/hmmer/ParamSetFactory.java b/src/jalview/ws2/hmmer/ParamSetFactory.java
new file mode 100644 (file)
index 0000000..211b9de
--- /dev/null
@@ -0,0 +1,139 @@
+package jalview.ws2.hmmer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import jalview.hmmer.rest.PhmmerClient;
+import jalview.ws.params.ArgumentI;
+import jalview.ws.params.WsParamSetI;
+import jalview.ws.params.simple.*;
+
+class ParamSetFactory
+{
+  private static class SimpleParamSet implements WsParamSetI
+  {
+    private String name;
+    private String description;
+    private String[] applicableUrls;
+    private String sourceFile = null;
+    private List<ArgumentI> arguments;
+    
+    
+    @Override
+    public String getName()
+    {
+      return name;
+    }
+
+    @Override
+    public String getDescription()
+    {
+      return description;
+    }
+
+    @Override
+    public String[] getApplicableUrls()
+    {
+      return applicableUrls;
+    }
+
+    @Override
+    public String getSourceFile()
+    {
+      return sourceFile;
+    }
+
+    @Override
+    public void setSourceFile(String newfile)
+    {
+      this.sourceFile = newfile;
+    }
+
+    @Override
+    public boolean isModifiable()
+    {
+      return true;
+    }
+
+    @Override
+    public List<ArgumentI> getArguments()
+    {
+      return arguments;
+    }
+
+    @Override
+    public void setArguments(List<ArgumentI> args)
+    {
+      throw new UnsupportedOperationException();
+    }
+  }
+  
+  public static WsParamSetI newDefaultParamSet(PhmmerClient client)
+  {
+    SimpleParamSet paramSet = new SimpleParamSet();
+    paramSet.name = "Default";
+    paramSet.description = "Default set of parameters";
+    paramSet.applicableUrls = new String[]{ PhmmerClient.getDefaultURL() };
+    List<ArgumentI> arguments = new ArrayList<>();
+    var reqBuilder = client.newRequestBuilder();
+    
+    arguments.add(new StringParameter(
+        "Sequence Database", "Sequence Database Selection.", true,
+        reqBuilder.getDefaultDatabase(), reqBuilder.getDefaultDatabase(), 
+        reqBuilder.getAllowedDatabaseValues(),
+        reqBuilder.getAllowedDatabaseValues()));
+    arguments.add(new BooleanOption("Output Alignment", "Output alignment in result.",
+        false, reqBuilder.getDefaultAlignView(), reqBuilder.getDefaultAlignView(), null));
+    arguments.add(new IntegerParameter(
+        "Number of Hits Displayed", "Number of hits to be displayed.", false,
+        reqBuilder.getDefaultNhits(), 0, 100000));
+    
+    arguments.add(new RadioChoiceParameter(
+        "Cut-offs", "Cut-off type to be used.", List.of("E-value", "Bit score"),
+        "E-value"));
+    
+    arguments.add(new LogarithmicParameter(
+        "Significance E-values[Sequence]", "Significance E-values[Sequence]",
+        false, (double) reqBuilder.getDefaultIncE(), Double.MIN_VALUE, 10.0));
+    arguments.add(new LogarithmicParameter(
+        "Significance E-values[Hit]", "Significance E-values[Hit]", false,
+        (double) reqBuilder.getDefaultIncdomE(), Double.MIN_VALUE, 10.0));
+    arguments.add(new LogarithmicParameter(
+        "Report E-values[Sequence]", "Report E-values[Sequence]", false,
+        (double) reqBuilder.getDefaultE(), Double.MIN_VALUE, 10.0));
+    arguments.add(new LogarithmicParameter(
+        "Report E-values[Hit]", "Report E-values[Hit]", false,
+        (double) reqBuilder.getDefaultDomE(), Double.MIN_VALUE, 10.0));
+    
+    arguments.add(new DoubleParameter(
+        "Significance bit scores[Sequence]", "Significance bit scores[Sequence]",
+        false, (double) reqBuilder.getDefaultIncT(), 0.0, 10000.0));
+    arguments.add(new DoubleParameter(
+        "Significance bit scores[Hit]", "Significance bit scores[Hit]",
+        false, (double) reqBuilder.getDefaultIncdomT(), 0.0, 10000.0));
+    arguments.add(new DoubleParameter(
+        "Report bit scores[Sequence]", "Report bit scores[Sequence]",
+        false, (double) reqBuilder.getDefaultT(), 0.0, 10000.0));
+    arguments.add(new DoubleParameter(
+        "Report bit scores[Hit]", "Report bit scores[Hit]", false,
+        (double) reqBuilder.getDefaultDomT(), 0.0, 10000.0));
+    
+    arguments.add(new DoubleParameter(
+        "Gap Penalties[open]", "Gap Penalties[open]", false,
+        (double) reqBuilder.getDefaultPopen(), 0.0, 0.5));
+    arguments.add(new DoubleParameter(
+        "Gap Penalties[extend]", "Gap Penalties[extend]", false,
+        (double) reqBuilder.getDefaultPextend(), 0.0, 1.0));
+    arguments.add(new StringParameter(
+        "Gap Penalties[Substitution scoring matrix]",
+        "Gap Penalties[Substitution scoring matrix]",
+        false, reqBuilder.getDefaultMx(), reqBuilder.getDefaultMx(),
+        reqBuilder.getAllowedMxValues(), reqBuilder.getAllowedMxValues()));
+    arguments.add(new BooleanOption(
+        "No bias filter", "Disable bias composition filter which is on by default",
+        false, reqBuilder.getDefaultNoBias(), reqBuilder.getDefaultNoBias(), null));
+    
+    paramSet.arguments = arguments;
+    return paramSet;
+  }
+}
diff --git a/src/jalview/ws2/hmmer/PhmmerParamDatastore.java b/src/jalview/ws2/hmmer/PhmmerParamDatastore.java
new file mode 100644 (file)
index 0000000..63b7df8
--- /dev/null
@@ -0,0 +1,79 @@
+package jalview.ws2.hmmer;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import jalview.hmmer.rest.PhmmerClient;
+import jalview.ws.params.ArgumentI;
+import jalview.ws.params.ParamDatastoreI;
+import jalview.ws.params.WsParamSetI;
+
+public class PhmmerParamDatastore implements ParamDatastoreI
+{
+  private WsParamSetI defaultPreset;
+  private List<WsParamSetI> presets = List.of();
+  
+  public PhmmerParamDatastore(PhmmerClient client) {
+    defaultPreset = ParamSetFactory.newDefaultParamSet(client);
+  }
+  
+  @Override
+  public List<WsParamSetI> getPresets()
+  {
+    return presets;
+  }
+
+  @Override
+  public WsParamSetI getPreset(String name)
+  {
+    for (WsParamSetI preset : presets)
+      if (preset.getName().equals(name))
+        return preset;
+    return null;
+  }
+
+  @Override
+  public List<ArgumentI> getServiceParameters()
+  {
+    return Collections.unmodifiableList(defaultPreset.getArguments());
+  }
+
+  @Override
+  public boolean presetExists(String name)
+  {
+    for (WsParamSetI preset : presets)
+      if (preset.getName().equals(name))
+        return true;
+    return false;
+  }
+
+  @Override
+  public void deletePreset(String name)
+  {
+  }
+
+  @Override
+  public void storePreset(String presetName, String text, List<ArgumentI> jobParams)
+  {
+  }
+
+  @Override
+  public void updatePreset(String oldName, String presetName, String text, List<ArgumentI> jobParams)
+  {
+  }
+
+  @Override
+  public WsParamSetI parseServiceParameterFile(String name, String description, String[] serviceURL, String parameters)
+      throws IOException
+  {
+    return null;
+  }
+
+  @Override
+  public String generateServiceParameterFile(WsParamSetI pset) throws IOException
+  {
+    return null;
+  }
+
+}
diff --git a/src/jalview/ws2/hmmer/PhmmerWSDiscoverer.java b/src/jalview/ws2/hmmer/PhmmerWSDiscoverer.java
new file mode 100644 (file)
index 0000000..a030fa2
--- /dev/null
@@ -0,0 +1,185 @@
+package jalview.ws2.hmmer;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import jalview.bin.Cache;
+import jalview.hmmer.rest.PhmmerClient;
+import jalview.ws2.OperationsChangeListenerList;
+import jalview.ws2.WebServiceDiscovererI;
+import jalview.ws2.operations.Operation;
+import jalview.ws2.operations.PhmmerOperation;
+
+import static java.lang.String.format;
+
+
+public class PhmmerWSDiscoverer implements WebServiceDiscovererI
+{
+  private static final String DEFAULT_PHMMER_URL = PhmmerClient.getDefaultURL();
+  
+  private static PhmmerWSDiscoverer instance = null;
+  
+  private List<Operation> operations = List.of();
+
+  private PhmmerWSDiscoverer()
+  {
+  }
+  
+  public static PhmmerWSDiscoverer getInstance()
+  {
+    if (instance == null)
+      instance = new PhmmerWSDiscoverer();
+    return instance;
+  }
+  
+  @Override
+  public List<String> getUrls()
+  {
+    return List.of(DEFAULT_PHMMER_URL);
+  }
+
+  @Override
+  public void setUrls(List<String> wsUrls)
+  {
+    throw new UnsupportedOperationException("setting url is not supported");
+  }
+
+  @Override
+  public boolean testUrl(URL url)
+  {
+    return getStatusForUrl(url.toString()) == STATUS_OK;
+  }
+
+  @Override
+  public int getStatusForUrl(String url)
+  {
+    try {
+      PhmmerClient.create(url);
+      return STATUS_OK;
+    }
+    catch (IOException e) {
+      return STATUS_INVALID;
+    }
+  }
+
+  @Override
+  public List<Operation> getOperations()
+  {
+    return operations;
+  }
+
+  @Override
+  public boolean hasServices()
+  {
+    return !isRunning() && operations.size() > 0;
+  }
+  
+  private static final int END = 0x01;
+  private static final int BEGIN = 0x02;
+  private static final int AGAIN = 0x04;
+  private final AtomicInteger state = new AtomicInteger(END);
+  private CompletableFuture<WebServiceDiscovererI> discoveryTask = new CompletableFuture<>(); 
+
+  @Override
+  public boolean isRunning()
+  {
+    return (state.get() & (BEGIN | AGAIN)) != 0;
+  }
+
+  @Override
+  public boolean isDone()
+  {
+    return state.get() == END && discoveryTask != null && discoveryTask.isDone();
+  }
+
+  @Override
+  public synchronized CompletableFuture<WebServiceDiscovererI> startDiscoverer()
+  {
+    while (true)
+    {
+      if (state.get() == AGAIN)
+      {
+        break;
+      }
+      else if (state.compareAndSet(END, BEGIN) || state.compareAndSet(BEGIN, AGAIN))
+      {
+        final var oldTask = discoveryTask;
+        CompletableFuture<WebServiceDiscovererI> task = oldTask
+            .handleAsync((r, e) -> {
+              reloadServices();
+              return PhmmerWSDiscoverer.this;
+            });
+        task.thenRun(() -> {
+          while (true)
+          {
+            if (state.compareAndSet(BEGIN, END) || state.compareAndSet(AGAIN, BEGIN))
+            {
+              break;
+            }
+          }
+          fireOperationsChanged(getOperations());
+        });
+        oldTask.cancel(false);
+        discoveryTask = task;
+        break;
+      }
+    }
+    return discoveryTask;
+  }
+  
+  private List<Operation> reloadServices()
+  {
+    fireOperationsChanged(Collections.emptyList());
+    ArrayList<Operation> allOperations = new ArrayList<>();
+    for (String url : getUrls())
+    {
+      PhmmerClient client;
+      try 
+      {
+        client = PhmmerClient.create(url);
+      } catch (IOException e)
+      {
+        Cache.log.error(format("Unable to create phmmer client for url %s", url), e);
+        continue;
+      }
+      PhmmerWebService webService = new PhmmerWebService(client);
+      PhmmerOperation operation = new PhmmerOperation(webService, webService::getPhmmerResult);
+      allOperations.add(operation);
+    }
+    this.operations = Collections.unmodifiableList(allOperations);
+    Cache.log.info("Reloading phmmer services finished");
+    return this.operations;
+  }
+
+  @Override
+  public String getErrorMessages()
+  {
+    return "";
+  }
+  
+  private OperationsChangeListenerList operationsChangeListeners =
+      new OperationsChangeListenerList(this);
+
+  @Override
+  public void addOperationsChangeListener(OperationsChangeListener listener)
+  {
+    operationsChangeListeners.addListener(listener);
+  }
+
+  @Override
+  public void removeOperationsChangeListener(OperationsChangeListener listener)
+  {
+    operationsChangeListeners.removeListener(listener);
+  }
+  
+  private void fireOperationsChanged(List<Operation> list)
+  {
+    operationsChangeListeners.fireOperationsChanged(list);
+  }
+
+}
diff --git a/src/jalview/ws2/hmmer/PhmmerWebService.java b/src/jalview/ws2/hmmer/PhmmerWebService.java
new file mode 100644 (file)
index 0000000..297906c
--- /dev/null
@@ -0,0 +1,203 @@
+package jalview.ws2.hmmer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import jalview.bin.Cache;
+import jalview.datamodel.SequenceI;
+import jalview.hmmer.rest.PhmmerClient;
+import jalview.hmmer.rest.PhmmerRequest;
+import jalview.hmmer.rest.PhmmerRequestBuilder;
+import jalview.io.FileFormat;
+import jalview.ws.params.ArgumentI;
+import jalview.ws.params.ParamDatastoreI;
+import jalview.ws2.WSJob;
+import jalview.ws2.WSJobStatus;
+import jalview.ws2.WebServiceI;
+
+import static java.lang.String.format;
+
+public class PhmmerWebService implements WebServiceI
+{
+  protected final PhmmerClient client;
+  protected ParamDatastoreI store;
+  
+  public PhmmerWebService(PhmmerClient client) {
+    this.client = client;
+  }
+
+  @Override
+  public String getHostName()
+  {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+  @Override
+  public String getProviderName()
+  {
+    return "EBI HMMER";
+  }
+
+  @Override
+  public String getName()
+  {
+    return "phmmer";
+  }
+
+  @Override
+  public String getDescription()
+  {
+    return "HMMER phmmer searches a database of protein sequence with "
+        + "a protein sequence (protein sequence vs protein sequence database).";
+  }
+
+  @Override
+  public boolean hasParameters()
+  {
+    return getParamStore().getServiceParameters().size() > 0;
+  }
+
+  @Override
+  public ParamDatastoreI getParamStore()
+  {
+    if (store == null)
+      store = new PhmmerParamDatastore(client);
+    return store;
+  }
+
+  @Override
+  public String submit(List<SequenceI> sequences, List<ArgumentI> args) throws IOException
+  {
+    PhmmerRequestBuilder reqBuilder = client.newRequestBuilder();
+    String sequence = FileFormat.Fasta.getWriter(null)
+        .print(new SequenceI[]{ sequences.get(0) }, false);
+    reqBuilder.sequenceString(sequence);
+    boolean useBitScore = false;
+    for (ArgumentI arg : args) {
+      String value = arg.getValue();
+      if (value == null)
+        continue;
+      switch(arg.getName()) {
+      case "Sequence Database":
+        reqBuilder.database(value);
+        break;
+      case "Output Alignment":
+        reqBuilder.alignView(!value.isBlank() ? true : false);
+        break;
+      case "Number of Hits Displayed":
+        reqBuilder.nhits(Integer.parseInt(value));
+        break;
+      case "Cut-offs":
+        if (value.equals("E-value"))
+          useBitScore = false;
+        else if (value.equals("Bit score"))
+          useBitScore = true;
+        else
+          throw new IllegalArgumentException(format(
+              "Illegal cut off value \"%s\".", value));
+        break;
+      case "Gap Penalties[open]":
+        reqBuilder.popen(Float.parseFloat(value));
+        break;
+      case "Gap Penalties[extend]":
+        reqBuilder.popen(Float.parseFloat(value));
+        break;
+      case "Gap Penalties[Substitution scoring matrix]":
+        reqBuilder.mx(value);
+        break;
+      case "No bias filter":
+        reqBuilder.noBias(!value.isBlank() ? true : false);
+        break;
+      }
+    }
+    for (ArgumentI arg : args) {
+      String value = arg.getValue();
+      if (value == null)
+        continue;
+      switch (arg.getName()) {
+      case "Significance E-values[Sequence]":
+        if (useBitScore) break;
+        reqBuilder.incE(Float.parseFloat(value));
+        break;
+      case "Significance E-values[Hit]":
+        if (useBitScore) break;
+        reqBuilder.incdomE(Float.parseFloat(value));
+        break;
+      case "Report E-values[Sequence]":
+        if (useBitScore) break;
+        reqBuilder.E(Float.parseFloat(value));
+        break;
+      case "Report E-values[Hit]":
+        if (useBitScore) break;
+        reqBuilder.domE(Float.parseFloat(value));
+        break;
+      case "Significance bit scores[Sequence]":
+        if (!useBitScore) break;
+        reqBuilder.incT(Float.parseFloat(value));
+        break;
+      case "Significance bit scores[Hit]":
+        if (!useBitScore) break;
+        reqBuilder.incdomT(Float.parseFloat(value));
+        break;
+      case "Report bit scores[Sequence]":
+        if (!useBitScore) break;
+        reqBuilder.T(Float.parseFloat(value));
+        break;
+      case "Report bit scores[Hit]":
+        if (!useBitScore) break;
+        reqBuilder.domT(Float.parseFloat(value));
+        break;
+      }
+    }
+    PhmmerRequest request = reqBuilder.build();
+    return client.submitRequest(request, "user@example.org");
+  }
+
+  @Override
+  public void updateProgress(WSJob job) throws IOException
+  {
+    var status = client.pollStatus(job.getJobId());
+    switch(status) {
+    case PENDING:
+      job.setStatus(WSJobStatus.QUEUED); break;
+    case RUNNING:
+      job.setStatus(WSJobStatus.RUNNING); break;
+    case FINISHED:
+      job.setStatus(WSJobStatus.FINISHED); break;
+    case FAILURE:
+      job.setStatus(WSJobStatus.FAILED); break;
+    case NOT_FOUND:
+      job.setStatus(WSJobStatus.UNKNOWN); break;
+    case UNDEFINED:
+      job.setStatus(WSJobStatus.UNKNOWN); break;
+    }
+  }
+
+  @Override
+  public void cancel(WSJob job) throws IOException
+  {
+    job.setStatus(WSJobStatus.CANCELLED);
+    Cache.log.warn("phmmer does not implement job cancellation.");
+  }
+
+  @Override
+  public boolean handleSubmissionError(WSJob job, Exception ex)
+  {
+    return false;
+  }
+
+  @Override
+  public boolean handleCollectionError(WSJob job, Exception ex)
+  {
+    return false;
+  }
+  
+  public Object getPhmmerResult(WSJob job) throws IOException
+  {
+    InputStream is = client.getFileStream(job.getJobId(), "out");
+    return new String(is.readAllBytes(), "UTF-8");
+  }
+
+}
diff --git a/src/jalview/ws2/operations/PhmmerOperation.java b/src/jalview/ws2/operations/PhmmerOperation.java
new file mode 100644 (file)
index 0000000..4c769f5
--- /dev/null
@@ -0,0 +1,42 @@
+package jalview.ws2.operations;
+
+import java.io.IOException;
+
+import jalview.ws2.WSJob;
+import jalview.ws2.WebServiceI;
+import jalview.ws2.gui.PhmmerMenuBuilder;
+import jalview.ws2.gui.MenuEntryProviderI;
+
+public class PhmmerOperation extends AbstractOperation
+{
+  /**
+   * Interface that the web service client must implement in order to
+   * support phmmer operations.
+   * 
+   * TODO: Change the name of the interface and its method.
+   * TODO: Change the returned type. 
+   */
+  public static interface PhmmerResultSupplier {
+    public Object getPhmmerResult(WSJob job) throws IOException;
+  }
+  
+  private PhmmerResultSupplier resultSupplier;
+  
+  public PhmmerOperation(WebServiceI service, PhmmerResultSupplier resultSupplier)
+  {
+    super(service, "HMMER");
+    this.resultSupplier = resultSupplier;
+  }
+
+  @Override
+  public MenuEntryProviderI getMenuBuilder()
+  {
+    return new PhmmerMenuBuilder(this);
+  }
+  
+  public PhmmerResultSupplier getResultSupplier()
+  {
+    return resultSupplier;
+  }
+
+}
diff --git a/src/jalview/ws2/operations/PhmmerWorker.java b/src/jalview/ws2/operations/PhmmerWorker.java
new file mode 100644 (file)
index 0000000..dc61b84
--- /dev/null
@@ -0,0 +1,91 @@
+package jalview.ws2.operations;
+
+import static java.lang.String.format;
+
+import java.io.IOException;
+import java.util.List;
+
+import jalview.bin.Cache;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignViewport;
+import jalview.ws.params.ArgumentI;
+import jalview.ws2.WSJob;
+import jalview.ws2.WebServiceI;
+
+
+public class PhmmerWorker extends AbstractPollableWorker
+{
+  
+  // TODO store additional job information in the job object
+  private static class PhmmerJob extends WSJob 
+  {
+    private PhmmerJob(String serviceProvider, String serviceName, String hostName)
+    {
+      super(serviceProvider, serviceName, hostName);
+    }
+  }
+  
+  private final PhmmerOperation operation;
+  private final AlignmentView msa;
+  private final List<ArgumentI> args;
+  private WSJobList<PhmmerJob> jobs = new WSJobList<>();
+  private PhmmerJob job = null;
+  
+  // TODO add constructor arguments which are necessary to run the job.
+  public PhmmerWorker(PhmmerOperation operation, AlignmentView msa, List<ArgumentI> args)
+  {
+    this.operation = operation;
+    this.msa = msa;
+    this.args = args;
+  }
+  
+  @Override
+  public void start() throws IOException
+  {
+    Cache.log.info(format("Starting new %s job", operation.getName()));
+    /* TODO prepare input data and submit the job
+     * this is currently a placeholder which fetches all sequences
+     * the client submits only the first sequence */  
+    List<SequenceI> sequences = msa.getVisibleAlignment('-').getSequences();
+    WebServiceI client = operation.getWebService();
+    job = new PhmmerJob(client.getProviderName(), client.getName(), client.getHostName());
+    jobs.add(job);
+    listeners.fireJobCreated(job);
+    String jobId = client.submit(sequences, args);
+    job.setJobId(jobId);
+    Cache.log.debug(format("Service %s: submitted job id %s", operation.getHostName(), jobId));
+    listeners.fireWorkerStarted();
+  }
+
+  @Override
+  public void done()
+  {
+    /* TODO when the job is finished, get the result from the supplier
+     * and do whatever is needed to display the result. For now, the result
+     * is printed to the logger */
+    Object result;
+    try
+    {
+      result = operation.getResultSupplier().getPhmmerResult(job);
+    }
+    catch (IOException e) {
+      Cache.log.debug(format("Failed to get results for job %s.", job.toString()));
+      return;
+    }
+    Cache.log.debug(result.toString());
+  }
+
+  @Override
+  public Operation getOperation()
+  {
+    return operation;
+  }
+
+  @Override
+  public WSJobList<? extends WSJob> getJobs()
+  {
+    return jobs;
+  }
+  
+}