Merge branch 'JAL-3878_ws-overhaul-3' into mmw/Release_2_12_ws_merge
[jalview.git] / src / jalview / ws2 / client / slivka / SlivkaWSDiscoverer.java
diff --git a/src/jalview/ws2/client/slivka/SlivkaWSDiscoverer.java b/src/jalview/ws2/client/slivka/SlivkaWSDiscoverer.java
new file mode 100644 (file)
index 0000000..58f6d67
--- /dev/null
@@ -0,0 +1,245 @@
+package jalview.ws2.client.slivka;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import jalview.bin.Cache;
+import jalview.ws.params.ParamManager;
+import jalview.ws2.actions.alignment.AlignmentAction;
+import jalview.ws2.actions.annotation.AnnotationAction;
+import jalview.ws2.api.WebService;
+import jalview.ws2.client.api.AbstractWebServiceDiscoverer;
+import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
+import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
+
+public class SlivkaWSDiscoverer extends AbstractWebServiceDiscoverer
+{
+  private static final String SLIVKA_HOST_URLS = "SLIVKAHOSTURLS";
+
+  private static final URL DEFAULT_URL;
+  static
+  {
+    try
+    {
+      DEFAULT_URL = new URL("https://www.compbio.dundee.ac.uk/slivka/");
+    } catch (MalformedURLException e)
+    {
+      throw new AssertionError(e);
+    }
+  }
+
+  private static SlivkaWSDiscoverer instance = null;
+
+  private static ParamManager paramManager = null;
+
+  private SlivkaWSDiscoverer()
+  {
+  }
+
+  public static SlivkaWSDiscoverer getInstance()
+  {
+    if (instance == null)
+      instance = new SlivkaWSDiscoverer();
+    return instance;
+  }
+
+  public static void setParamManager(ParamManager manager)
+  {
+    paramManager = manager;
+  }
+
+  @Override
+  public int getStatusForUrl(URL url)
+  {
+    try
+    {
+      List<?> services = new SlivkaClient(url.toString()).getServices();
+      return services.isEmpty() ? STATUS_NO_SERVICES : STATUS_OK;
+    } catch (IOException e)
+    {
+      Cache.log.error("slivka could not retrieve services from " + url, e);
+      return STATUS_INVALID;
+    }
+  }
+
+  @Override
+  protected String getUrlsPropertyKey()
+  {
+    return SLIVKA_HOST_URLS;
+  }
+
+  @Override
+  protected URL getDefaultUrl()
+  {
+    return DEFAULT_URL;
+  }
+
+  @Override
+  protected List<WebService<?>> fetchServices(URL url) throws IOException
+  {
+    ArrayList<WebService<?>> allServices = new ArrayList<>();
+    SlivkaClient slivkaClient;
+    try
+    {
+      slivkaClient = new SlivkaClient(url.toURI());
+    } catch (URISyntaxException e)
+    {
+      throw new MalformedURLException(e.getMessage());
+    }
+    for (var slivkaService : slivkaClient.getServices())
+    {
+      int serviceClass = getServiceClass(slivkaService);
+      if (serviceClass == SERVICE_CLASS_MSA)
+      {
+        var wsb = WebService.<AlignmentAction> newBuilder();
+        initServiceBuilder(slivkaService, wsb);
+        wsb.category("Alignment");
+        wsb.interactive(false);
+        wsb.actionClass(AlignmentAction.class);
+        var msaService = wsb.build();
+
+        boolean canRealign = msaService.getName().contains("lustal");
+        var client = new SlivkaAlignmentWSClient(slivkaService);
+        var actionBuilder = AlignmentAction.newBuilder(client);
+        actionBuilder.name("Alignment");
+        actionBuilder.webService(msaService);
+        if (canRealign)
+          actionBuilder.subcategory("Align");
+        actionBuilder.minSequences(2);
+        msaService.addAction(actionBuilder.build());
+        if (canRealign)
+        {
+          actionBuilder.name("Re-alignment");
+          actionBuilder.subcategory("Realign");
+          actionBuilder.submitGaps(true);
+          msaService.addAction(actionBuilder.build());
+        }
+        allServices.add(msaService);
+      }
+      else if (serviceClass == SERVICE_CLASS_PROT_SEQ_ANALYSIS)
+      {
+        var wsb = WebService.<AnnotationAction> newBuilder();
+        initServiceBuilder(slivkaService, wsb);
+        wsb.category("Protein Disorder");
+        wsb.interactive(false);
+        wsb.actionClass(AnnotationAction.class);
+        var psaService = wsb.build();
+        var client = new SlivkaAnnotationWSClient(slivkaService);
+        var actionBuilder = AnnotationAction.newBuilder(client);
+        actionBuilder.webService(psaService);
+        actionBuilder.name("Analysis");
+        psaService.addAction(actionBuilder.build());
+        allServices.add(psaService);
+      }
+      else if (serviceClass == SERVICE_CLASS_CONSERVATION)
+      {
+        var wsb = WebService.<AnnotationAction> newBuilder();
+        initServiceBuilder(slivkaService, wsb);
+        wsb.category("Conservation");
+        wsb.interactive(true);
+        wsb.actionClass(AnnotationAction.class);
+        var conService = wsb.build();
+        var client = new SlivkaAnnotationWSClient(slivkaService);
+        var actionBuilder = AnnotationAction.newBuilder(client);
+        actionBuilder.webService(conService);
+        actionBuilder.name("");
+        actionBuilder.alignmentAnalysis(true);
+        actionBuilder.requireAlignedSequences(true);
+        actionBuilder.filterSymbols(true);
+        conService.addAction(actionBuilder.build());
+        allServices.add(conService);
+      }
+      else if (serviceClass == SERVICE_CLASS_RNA_SEC_STR_PRED)
+      {
+        var wsb = WebService.<AnnotationAction> newBuilder();
+        initServiceBuilder(slivkaService, wsb);
+        wsb.category("Secondary Structure Prediction");
+        wsb.interactive(true);
+        wsb.actionClass(AnnotationAction.class);
+        var predService = wsb.build();
+        var client = new SlivkaAnnotationWSClient(slivkaService);
+        var actionBuilder = AnnotationAction.newBuilder(client);
+        actionBuilder.webService(predService);
+        actionBuilder.name("Prediction");
+        actionBuilder.minSequences(2);
+        actionBuilder.allowNucleotide(true);
+        actionBuilder.allowProtein(false);
+        actionBuilder.alignmentAnalysis(true);
+        actionBuilder.requireAlignedSequences(true);
+        actionBuilder.filterSymbols(false);
+        predService.addAction(actionBuilder.build());
+        allServices.add(predService);
+      }
+      else
+      {
+        continue;
+      }
+    }
+    return allServices;
+  }
+
+  private void initServiceBuilder(SlivkaService service, WebService.Builder<?> wsBuilder)
+  {
+    try
+    {
+      wsBuilder.url(service.getClient().getUrl().toURL());
+    } catch (MalformedURLException e)
+    {
+      e.printStackTrace();
+    }
+    wsBuilder.clientName("slivka");
+    wsBuilder.name(service.getName());
+    wsBuilder.description(service.getDescription());
+    var storeBuilder = new SlivkaParamStoreFactory(service, paramManager);
+    wsBuilder.paramDatastore(storeBuilder.createParamDatastore());
+  }
+
+  static final int SERVICE_CLASS_UNSUPPORTED = -1;
+
+  static final int SERVICE_CLASS_MSA = 1;
+
+  static final int SERVICE_CLASS_RNA_SEC_STR_PRED = 2;
+
+  static final int SERVICE_CLASS_CONSERVATION = 3;
+
+  static final int SERVICE_CLASS_PROT_SEQ_ANALYSIS = 4;
+
+  static final int SERVICE_CLASS_PROT_SEC_STR_PRED = 5;
+
+  /**
+   * Scan service classifiers starting with operation :: analysis to decide the
+   * operation class.
+   * 
+   * @return service class flag
+   */
+  private static int getServiceClass(SlivkaService service)
+  {
+    for (String classifier : service.getClassifiers())
+    {
+      String[] path = classifier.split("\\s*::\\s*");
+      if (path.length < 3 || !path[0].equalsIgnoreCase("operation") ||
+          !path[1].equalsIgnoreCase("analysis"))
+        continue;
+      // classifier is operation :: analysis :: *
+      var tail = path[path.length - 1].toLowerCase();
+      switch (tail)
+      {
+      case "multiple sequence alignment":
+        return SERVICE_CLASS_MSA;
+      case "rna secondary structure prediction":
+        return SERVICE_CLASS_RNA_SEC_STR_PRED;
+      case "sequence alignment analysis (conservation)":
+        return SERVICE_CLASS_CONSERVATION;
+      case "protein sequence analysis":
+        return SERVICE_CLASS_PROT_SEQ_ANALYSIS;
+      case "protein secondary structure prediction":
+        return SERVICE_CLASS_PROT_SEC_STR_PRED;
+      }
+    }
+    return SERVICE_CLASS_UNSUPPORTED;
+  }
+}