Merge branch 'develop' into update_212_Dec_merge_with_21125_chamges
[jalview.git] / src / jalview / ws / slivkaws / SlivkaWSDiscoverer.java
index b799c5a..d21d5d1 100644 (file)
 package jalview.ws.slivkaws;
 
-import jalview.datamodel.AlignmentView;
-import jalview.gui.AlignFrame;
-import jalview.ws.WSMenuEntryProviderI;
-import jalview.ws.jws2.MsaWSClient;
+import jalview.bin.Cache;
+import jalview.bin.Console;
+import jalview.ws.ServiceChangeListener;
+import jalview.ws.WSDiscovererI;
+import jalview.ws.api.ServiceWithParameters;
+import javajs.http.HttpClientFactory;
 
-import java.awt.event.ActionEvent;
-import java.net.URISyntaxException;
-
-import javax.swing.JMenu;
-import javax.swing.JMenuItem;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 
+import compbio.data.msa.Category;
 import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
+import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
 
-public class SlivkaWSDiscoverer
-  implements Runnable, WSMenuEntryProviderI
+public class SlivkaWSDiscoverer implements WSDiscovererI
 {
+  private static final String SLIVKA_HOST_URLS = "SLIVKAHOSTURLS";
+
+  private static final String COMPBIO_SLIVKA = "https://www.compbio.dundee.ac.uk/slivka/";
+
   private static SlivkaWSDiscoverer instance = null;
-  private SlivkaClient client;
-  private ClustaloWS clustalo;
 
-  private SlivkaWSDiscoverer() {
-    try
+  private List<ServiceWithParameters> services = List.of();
+
+  private SlivkaWSDiscoverer()
+  {
+  }
+
+  public static SlivkaWSDiscoverer getInstance()
+  {
+    if (instance == null)
     {
-      client = new SlivkaClient("gjb-www-1.cluster.lifesci.dundee.ac.uk", 3203);
-    } catch (URISyntaxException e)
+      instance = new SlivkaWSDiscoverer();
+    }
+    return instance;
+  }
+
+  private Set<ServiceChangeListener> serviceListeners = new CopyOnWriteArraySet<>();
+
+  @Override
+  public void addServiceChangeListener(ServiceChangeListener l)
+  {
+    serviceListeners.add(l);
+  }
+
+  @Override
+  public void removeServiceChangeListener(ServiceChangeListener l)
+  {
+    serviceListeners.remove(l);
+  }
+
+  public void notifyServiceListeners(List<ServiceWithParameters> services)
+  {
+    for (var listener : serviceListeners)
     {
-      throw new RuntimeException(e);
+      listener.servicesChanged(this, services);
     }
-    clustalo = new ClustaloWS(client);
   }
 
-  public static SlivkaWSDiscoverer getInstance()
+  private final ExecutorService executor = Executors
+          .newSingleThreadExecutor();
+
+  private Vector<Future<?>> discoveryTasks = new Vector<>();
+
+  public CompletableFuture<WSDiscovererI> startDiscoverer()
   {
-    if (instance == null) {
-               instance = new SlivkaWSDiscoverer();
-       }
-    return instance;
+    CompletableFuture<WSDiscovererI> task = CompletableFuture
+            .supplyAsync(() -> {
+              reloadServices();
+              return SlivkaWSDiscoverer.this;
+            }, executor);
+    discoveryTasks.add(task);
+    return task;
+  }
+
+  private List<ServiceWithParameters> reloadServices()
+  {
+    Console.info("Reloading Slivka services");
+    notifyServiceListeners(Collections.emptyList());
+    ArrayList<ServiceWithParameters> instances = new ArrayList<>();
+
+    for (String url : getServiceUrls())
+    {
+      SlivkaClient client = new SlivkaClient(url);
+
+      List<SlivkaService> services;
+      try
+      {
+        services = client.getServices();
+      } catch (IOException e)
+      {
+        e.printStackTrace();
+        continue;
+      }
+      for (SlivkaService service : services)
+      {
+        SlivkaWSInstance newInstance = null;
+        for (String classifier : service.classifiers)
+        {
+          String[] path = classifier.split("\\s*::\\s*");
+          if (path.length >= 3 && path[0].toLowerCase().equals("operation")
+                  && path[1].toLowerCase().equals("analysis"))
+          {
+            switch (path[path.length - 1].toLowerCase())
+            {
+            case "rna secondary structure prediction":
+              newInstance = new RNAalifoldServiceInstance(client,
+                      service, "Secondary Structure Prediction");
+              break;
+            case "sequence alignment analysis (conservation)":
+              newInstance = new SlivkaAnnotationServiceInstance(client,
+                      service, Category.CATEGORY_CONSERVATION);
+              break;
+            case "protein sequence analysis":
+              newInstance = new SlivkaAnnotationServiceInstance(client,
+                      service, Category.CATEGORY_DISORDER);
+              break;
+            case "protein secondary structure prediction":
+              newInstance = new SlivkaAnnotationServiceInstance(client,
+                      service, "Secondary Structure Prediction");
+              break;
+            case "multiple sequence alignment":
+              newInstance = new SlivkaMsaServiceInstance(client, service,
+                      Category.CATEGORY_ALIGNMENT);
+              break;
+            }
+          }
+          if (newInstance != null)
+            break;
+        }
+        if (newInstance != null)
+          instances.add(newInstance);
+      }
+    }
+
+    services = instances;
+    Console.info("Slivka services reloading finished");
+    notifyServiceListeners(instances);
+    return instances;
+  }
+
+  @Override
+  public List<ServiceWithParameters> getServices()
+  {
+    return services;
+  }
+
+  @Override
+  public boolean hasServices()
+  {
+    return !isRunning() && services.size() > 0;
   }
 
   @Override
-  public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
+  public boolean isRunning()
   {
-    JMenu submenu = new JMenu("Slivka");
-    JMenuItem menuItem = new JMenuItem("ClustalO with defaults");
-    menuItem.addActionListener((ActionEvent e) -> {
+    return !discoveryTasks.stream().allMatch(Future::isDone);
+  }
 
-      AlignmentView msa = alignFrame.gatherSequencesForAlignment();
+  @Override
+  public void setServiceUrls(List<String> wsUrls)
+  {
+    if (wsUrls != null && !wsUrls.isEmpty())
+    {
+      Cache.setProperty(SLIVKA_HOST_URLS, String.join(",", wsUrls));
+    }
+    else
+    {
+      Cache.removeProperty(SLIVKA_HOST_URLS);
+    }
+  }
 
-      if (msa != null)
+  @Override
+  public List<String> getServiceUrls()
+  {
+    String surls = Cache.getDefault(SLIVKA_HOST_URLS, COMPBIO_SLIVKA);
+    String[] urls = surls.split(",");
+    ArrayList<String> valid = new ArrayList<>(urls.length);
+    for (String url : urls)
+    {
+      try
       {
-        new MsaWSClient(clustalo, alignFrame.getTitle(), msa, false,
-            true,
-            alignFrame.getViewport().getAlignment().getDataset(),
-            alignFrame);
+        new URL(url);
+        valid.add(url);
+      } catch (MalformedURLException e)
+      {
+        Console.warn("Problem whilst trying to make a URL from '"
+                + ((url != null) ? url : "<null>") + "'");
+        Console.warn(
+                "This was probably due to a malformed comma separated list"
+                        + " in the " + SLIVKA_HOST_URLS
+                        + " entry of $(HOME)/.jalview_properties)");
+        Console.debug("Exception was ", e);
       }
-//
-//      List<SequenceI> sequences = alignFrame.getViewport().getAlignment().getSequences();
-//      try
-//      {
-//        clustalo.align(sequences, null, null);
-//      } catch (FormValidationException | IOException exc)
-//      {
-//        throw new RuntimeException(exc);
-//      }
-    });
-    submenu.add(menuItem);
-    wsmenu.add(submenu);
+    }
+    return valid;
+  }
+
+  @Override
+  public boolean testServiceUrl(URL url)
+  {
+    return getServerStatusFor(url.toString()) == STATUS_OK;
   }
 
   @Override
-  public void run()
+  public int getServerStatusFor(String url)
   {
+    try
+    {
+      List<?> services = new SlivkaClient(url).getServices();
+      return services.isEmpty() ? STATUS_NO_SERVICES : STATUS_OK;
+    } catch (IOException | org.json.JSONException e)
+    {
+      Console.error("Slivka could not retrieve services list", e);
+      return STATUS_INVALID;
+    }
+  }
 
+  @Override
+  public String getErrorMessages()
+  {
+    // TODO Auto-generated method stub
+    return "";
   }
 }