JAL-3690 refactoring web-services discovery
[jalview.git] / src / jalview / ws / jws2 / Jws2Discoverer.java
index 77f2440..73d3700 100644 (file)
@@ -22,35 +22,31 @@ package jalview.ws.jws2;
 
 import jalview.bin.Cache;
 import jalview.gui.AlignFrame;
-import jalview.gui.Desktop;
-import jalview.gui.JvSwingUtils;
 import jalview.util.MessageManager;
-import jalview.ws.WSMenuEntryProviderI;
+import jalview.ws.ServiceChangeListener;
+import jalview.ws.WSDiscovererI;
 import jalview.ws.api.ServiceWithParameters;
 import jalview.ws.jws2.jabaws2.Jws2Instance;
 import jalview.ws.params.ParamDatastoreI;
 
-import java.awt.Color;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 import java.beans.PropertyChangeSupport;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
+import java.util.Collections;
 import java.util.HashSet;
-import java.util.Hashtable;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.StringTokenizer;
 import java.util.Vector;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
 
 import javax.swing.JMenu;
-import javax.swing.JMenuItem;
 
 import compbio.ws.client.Services;
 
@@ -61,7 +57,7 @@ import compbio.ws.client.Services;
  * @author JimP
  * 
  */
-public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
+public class Jws2Discoverer implements WSDiscovererI, Runnable
 {
   public static final String COMPBIO_JABAWS = "http://www.compbio.dundee.ac.uk/jabaws";
 
@@ -82,9 +78,8 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
 
   // preferred url has precedence over others
   private String preferredUrl;
-
-  private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
-          this);
+  
+  private Set<ServiceChangeListener> serviceListeners = new CopyOnWriteArraySet<>();
 
   private Vector<String> invalidServiceUrls = null;
 
@@ -95,8 +90,8 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
   private volatile boolean running = false;
 
   private volatile boolean aborted = false;
-
-  private Thread oldthread = null;
+  
+  private volatile Thread oldthread = null;
 
   /**
    * holds list of services.
@@ -110,28 +105,25 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
   {
   }
 
-  /**
-   * change listeners are notified of "services" property changes
-   * 
-   * @param listener
-   *          to be added that consumes new services Hashtable object.
-   */
-  public void addPropertyChangeListener(
-          java.beans.PropertyChangeListener listener)
+
+  @Override
+  public void addServiceChangeListener(ServiceChangeListener listener)
   {
-    changeSupport.addPropertyChangeListener(listener);
+    serviceListeners.add(listener);
   }
 
-  /**
-   * 
-   * 
-   * @param listener
-   *          to be removed
-   */
-  public void removePropertyChangeListener(
-          java.beans.PropertyChangeListener listener)
+  @Override
+  public void removeServiceChangeListener(ServiceChangeListener listener)
   {
-    changeSupport.removePropertyChangeListener(listener);
+    serviceListeners.remove(listener);
+  }
+
+  private void notifyServiceListeners(List<? extends ServiceWithParameters> services) 
+  {
+    if (services == null) services = this.services;
+    for (var listener : serviceListeners) {
+      listener.servicesChanged(this, services);
+    }
   }
 
   /**
@@ -186,13 +178,11 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
       ignoredServices.add(ignored);
     }
 
-    changeSupport.firePropertyChange("services", services,
-            new Vector<Jws2Instance>());
+    notifyServiceListeners(Collections.emptyList());
     oldthread = Thread.currentThread();
     try
     {
-      Class foo = getClass().getClassLoader()
-              .loadClass("compbio.ws.client.Jws2Client");
+      getClass().getClassLoader().loadClass("compbio.ws.client.Jws2Client");
     } catch (ClassNotFoundException e)
     {
       System.err.println(
@@ -250,7 +240,6 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
       } catch (Exception e)
       {
       }
-      ;
       for (JabaWsServerQuery squery : qrys)
       {
         if (squery.isRunning())
@@ -298,8 +287,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     }
     oldthread = null;
     running = false;
-    changeSupport.firePropertyChange("services", new Vector<Jws2Instance>(),
-            services);
+    notifyServiceListeners(services);
   }
 
   /**
@@ -336,366 +324,10 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
   }
 
   /**
-   * attach all available web services to the appropriate submenu in the given
-   * JMenu
-   */
-  @Override
-  public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
-  {
-    // dynamically regenerate service list.
-    populateWSMenuEntry(wsmenu, alignFrame, null);
-  }
-
-  private boolean isRecalculable(String action)
-  {
-    return (action != null && action.equalsIgnoreCase("conservation"));
-  }
-
-  private void populateWSMenuEntry(JMenu jws2al,
-          final AlignFrame alignFrame, String typeFilter)
-  {
-    if (running || services == null || services.size() == 0)
-    {
-      return;
-    }
-
-    /**
-     * eventually, JWS2 services will appear under the same align/etc submenus.
-     * for moment we keep them separate.
-     */
-    JMenu atpoint;
-    List<Jws2Instance> enumerableServices = new ArrayList<>();
-    // jws2al.removeAll();
-    Map<String, ServiceWithParameters> preferredHosts = new HashMap<>();
-    Map<String, List<ServiceWithParameters>> alternates = new HashMap<>();
-    for (Jws2Instance service : services.toArray(new Jws2Instance[0]))
-    {
-      // TODO: check this behaves with refactored serviceType to getName
-      if (!isRecalculable(service.getName()))
-      {
-        // add 'one shot' services to be displayed using the classic menu
-        // structure
-        enumerableServices.add(service);
-      }
-      else
-      {
-        if (!preferredHosts.containsKey(service.getName()))
-        {
-          ServiceWithParameters preferredInstance = getPreferredServiceFor(
-                  alignFrame, service.getName());
-          if (preferredInstance != null)
-          {
-            preferredHosts.put(service.getName(), preferredInstance);
-          }
-          else
-          {
-            preferredHosts.put(service.getName(), service);
-          }
-        }
-        List<ServiceWithParameters> ph = alternates.get(service.getName());
-        if (preferredHosts.get(service.getName()) != service)
-        {
-          if (ph == null)
-          {
-            ph = new ArrayList<>();
-          }
-          ph.add(service);
-          alternates.put(service.getName(), ph);
-        }
-      }
-
-    }
-
-    // create GUI element for classic services
-    addEnumeratedServices(jws2al, alignFrame, enumerableServices);
-    // and the instantaneous services
-    for (final ServiceWithParameters service : preferredHosts.values())
-    {
-      atpoint = JvSwingUtils.findOrCreateMenu(jws2al,
-              service.getServiceType());
-      JMenuItem hitm;
-      if (atpoint.getItemCount() > 1)
-      {
-        // previous service of this type already present
-        atpoint.addSeparator();
-      }
-      atpoint.add(hitm = new JMenuItem(service.getHostURL()));
-      hitm.setForeground(Color.blue);
-      hitm.addActionListener(new ActionListener()
-      {
-
-        @Override
-        public void actionPerformed(ActionEvent e)
-        {
-          Desktop.showUrl(service.getHostURL());
-        }
-      });
-      hitm.setToolTipText(JvSwingUtils.wrapTooltip(false,
-              MessageManager.getString("label.open_jabaws_web_page")));
-
-      service.attachWSMenuEntry(atpoint, alignFrame);
-      if (alternates.containsKey(service.getName()))
-      {
-        atpoint.add(hitm = new JMenu(
-                MessageManager.getString("label.switch_server")));
-        hitm.setToolTipText(JvSwingUtils.wrapTooltip(false,
-                MessageManager.getString("label.choose_jabaws_server")));
-        for (final ServiceWithParameters sv : alternates.get(service.getName()))
-        {
-          JMenuItem itm;
-          hitm.add(itm = new JMenuItem(sv.getHostURL()));
-          itm.setForeground(Color.blue);
-          itm.addActionListener(new ActionListener()
-          {
-
-            @Override
-            public void actionPerformed(ActionEvent arg0)
-            {
-              new Thread(new Runnable()
-              {
-                @Override
-                public void run()
-                {
-                  setPreferredServiceFor(alignFrame, sv.getName(),
-                          sv.getServiceType(), sv);
-                  changeSupport.firePropertyChange("services",
-                          new Vector<Jws2Instance>(), services);
-                };
-              }).start();
-
-            }
-          });
-        }
-      }
-    }
-  }
-
-  /**
-   * add services using the Java 2.5/2.6/2.7 system which optionally creates
-   * submenus to index by host and service program type
-   */
-  private void addEnumeratedServices(final JMenu jws2al,
-          final AlignFrame alignFrame,
-          List<Jws2Instance> enumerableServices)
-  {
-    boolean byhost = Cache.getDefault("WSMENU_BYHOST", false),
-            bytype = Cache.getDefault("WSMENU_BYTYPE", false);
-    /**
-     * eventually, JWS2 services will appear under the same align/etc submenus.
-     * for moment we keep them separate.
-     */
-    JMenu atpoint;
-
-    List<String> hostLabels = new ArrayList<>();
-    Hashtable<String, String> lasthostFor = new Hashtable<>();
-    Hashtable<String, ArrayList<Jws2Instance>> hosts = new Hashtable<>();
-    ArrayList<String> hostlist = new ArrayList<>();
-    for (Jws2Instance service : enumerableServices)
-    {
-      ArrayList<Jws2Instance> hostservices = hosts
-              .get(service.getHostURL());
-      if (hostservices == null)
-      {
-        hosts.put(service.getHostURL(),
-                hostservices = new ArrayList<>());
-        hostlist.add(service.getHostURL());
-      }
-      hostservices.add(service);
-    }
-    // now add hosts in order of the given array
-    for (String host : hostlist)
-    {
-      Jws2Instance orderedsvcs[] = hosts.get(host)
-              .toArray(new Jws2Instance[1]);
-      String sortbytype[] = new String[orderedsvcs.length];
-      for (int i = 0; i < sortbytype.length; i++)
-      {
-        sortbytype[i] = orderedsvcs[i].getName();
-      }
-      jalview.util.QuickSort.sort(sortbytype, orderedsvcs);
-      for (final Jws2Instance service : orderedsvcs)
-      {
-        atpoint = JvSwingUtils.findOrCreateMenu(jws2al,
-                service.getAction());
-        String type = service.getName();
-        if (byhost)
-        {
-          atpoint = JvSwingUtils.findOrCreateMenu(atpoint, host);
-          if (atpoint.getToolTipText() == null)
-          {
-            atpoint.setToolTipText(MessageManager
-                    .formatMessage("label.services_at", new String[]
-                    { host }));
-          }
-        }
-        if (bytype)
-        {
-          atpoint = JvSwingUtils.findOrCreateMenu(atpoint, type);
-          if (atpoint.getToolTipText() == null)
-          {
-            atpoint.setToolTipText(service.getActionText());
-          }
-        }
-        if (!byhost && !hostLabels.contains(
-                host + service.getName() + service.getActionText()))
-        // !hostLabels.contains(host + (bytype ?
-        // service.serviceType+service.getActionText() : "")))
-        {
-          // add a marker indicating where this service is hosted
-          // relies on services from the same host being listed in a
-          // contiguous
-          // group
-          JMenuItem hitm;
-          if (hostLabels.contains(host))
-          {
-            atpoint.addSeparator();
-          }
-          else
-          {
-            hostLabels.add(host);
-          }
-          if (lasthostFor.get(service.getAction()) == null
-                  || !lasthostFor.get(service.getAction()).equals(host))
-          {
-            atpoint.add(hitm = new JMenuItem(host));
-            hitm.setForeground(Color.blue);
-            hitm.addActionListener(new ActionListener()
-            {
-
-              @Override
-              public void actionPerformed(ActionEvent e)
-              {
-                Desktop.showUrl(service.getHostURL());
-              }
-            });
-            hitm.setToolTipText(
-                    JvSwingUtils.wrapTooltip(true, MessageManager
-                            .getString("label.open_jabaws_web_page")));
-            lasthostFor.put(service.getAction(), host);
-          }
-          hostLabels.add(
-                  host + service.getName() + service.getActionText());
-        }
-
-        service.attachWSMenuEntry(atpoint, alignFrame);
-      }
-    }
-  }
-
-  /**
-   * pick the user's preferred service based on a set of URLs (jaba server
-   * locations) and service URIs (specifying version and service interface
-   * class)
-   * 
-   * @param serviceURL
-   * @return null or best match for given uri/ls.
-   */
-  public ServiceWithParameters getPreferredServiceFor(String[] serviceURLs)
-  {
-    HashSet<String> urls = new HashSet<>();
-    urls.addAll(Arrays.asList(serviceURLs));
-    ServiceWithParameters match = null;
-    List<ServiceWithParameters> ourServices = getServices();
-
-    if (ourServices != null)
-    {
-      for (ServiceWithParameters svc : ourServices)
-      {
-        // TODO getNameURI Should return a versioned URI for the service, but
-        // doesn't as of 2.11
-        if (urls.contains(svc.getNameURI()))
-        {
-          if (match == null)
-          {
-            // for moment we always pick service from server ordered first in
-            // user's preferences
-            match = svc;
-          }
-          if (urls.contains(svc.getUri()))
-          {
-            // stop and return - we've matched type URI and URI for service
-            // endpoint
-            return svc;
-          }
-        }
-      }
-    }
-    return match;
-  }
-
-  Map<String, Map<String, String>> preferredServiceMap = new HashMap<>();
-
-  /**
-   * get current preferred endpoint of the given Jabaws service, or global
-   * default
    * 
-   * @param af
-   *          null or a specific alignFrame
-   * @param serviceName
-   *          Jws2Instance.getName() for service
-   * @return null if no service of this type is available, the preferred service
-   *         for the serviceType and af if specified and if defined.
+   * @param args
+   * @j2sIgnore
    */
-  public ServiceWithParameters getPreferredServiceFor(AlignFrame af,
-          String serviceName)
-  {
-    String serviceurl = null;
-    synchronized (preferredServiceMap)
-    {
-      String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
-      Map<String, String> prefmap = preferredServiceMap.get(afid);
-      if (afid.length() > 0 && prefmap == null)
-      {
-        // recover global setting, if any
-        prefmap = preferredServiceMap.get("");
-      }
-      if (prefmap != null)
-      {
-        serviceurl = prefmap.get(serviceName);
-      }
-
-    }
-    ServiceWithParameters response = null;
-    for (ServiceWithParameters svc : getServices())
-    {
-      if (svc.getName().equals(serviceName))
-      {
-        if (serviceurl == null || serviceurl.equals(svc.getHostURL()))
-        {
-          response = svc;
-          break;
-        }
-      }
-    }
-    return response;
-  }
-
-  public void setPreferredServiceFor(AlignFrame af, String serviceName,
-          String serviceAction, ServiceWithParameters selectedServer)
-  {
-    // TODO: pull out and generalise for the selectedServer's attributes
-    String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
-    if (preferredServiceMap == null)
-    {
-      preferredServiceMap = new HashMap<>();
-    }
-    Map<String, String> prefmap = preferredServiceMap.get(afid);
-    if (prefmap == null)
-    {
-      prefmap = new HashMap<>();
-      preferredServiceMap.put(afid, prefmap);
-    }
-    prefmap.put(serviceName, selectedServer.getHostURL());
-    prefmap.put(serviceAction, selectedServer.getHostURL());
-  }
-
-  public void setPreferredServiceFor(String serviceType,
-          String serviceAction, ServiceWithParameters selectedServer)
-  {
-    setPreferredServiceFor(null, serviceType, serviceAction,
-            selectedServer);
-  }
-
   public static void main(String[] args)
   {
     if (args.length > 0)
@@ -705,40 +337,29 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
       {
         testUrls.add(url);
       }
-      ;
     }
-    Thread runner = getDiscoverer()
-            .startDiscoverer(new PropertyChangeListener()
-            {
-
-              @Override
-              public void propertyChange(PropertyChangeEvent evt)
-              {
-                if (getDiscoverer().services != null)
-                {
-                  System.out.println("Changesupport: There are now "
-                          + getDiscoverer().services.size() + " services");
-                  int i = 1;
-                  for (ServiceWithParameters instance : getDiscoverer().services)
-                  {
-                    System.out.println("Service " + i++ + " "
-                            + instance.getClass() + "@"
-                            + instance.getHostURL()
-                            + ": " + instance.getActionText());
-                  }
-
-                }
-              }
-            });
-    while (runner.isAlive())
-    {
-      try
-      {
-        Thread.sleep(50);
-      } catch (InterruptedException e)
+    var discoverer = getDiscoverer();
+    discoverer.addServiceChangeListener((_discoverer, _services) -> {
+      if (discoverer.services != null)
       {
+        System.out.println("Changesupport: There are now "
+                + discoverer.services.size() + " services");
+        int i = 1;
+        for (ServiceWithParameters instance : discoverer.services)
+        {
+          System.out.println(
+                  "Service " + i++ + " " + instance.getClass()
+                          + "@" + instance.getHostURL() + ": "
+                          + instance.getActionText());
+        }
+
       }
-      ;
+    });
+    try
+    {
+      discoverer.startDiscoverer().get();
+    } catch (InterruptedException | ExecutionException e)
+    {
     }
     try
     {
@@ -762,16 +383,19 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     return discoverer;
   }
 
+  @Override
   public boolean hasServices()
   {
     return !running && services != null && services.size() > 0;
   }
 
+  @Override
   public boolean isRunning()
   {
     return running;
   }
 
+  @Override
   public void setServiceUrls(List<String> wsUrls)
   {
     if (wsUrls != null && !wsUrls.isEmpty())
@@ -798,6 +422,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
    * 
    * @return
    */
+  @Override
   public List<String> getServiceUrls()
   {
     if (testUrls != null)
@@ -851,10 +476,10 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     return urls;
   }
 
+  @Override
   public Vector<ServiceWithParameters> getServices()
   {
-    return (services == null) ? new Vector<>()
-            : new Vector<>(services);
+    return (services == null) ? new Vector<>() : new Vector<>(services);
   }
 
   /**
@@ -863,7 +488,8 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
    * @param foo
    * @return
    */
-  public static boolean testServiceUrl(URL foo)
+  @Override
+  public boolean testServiceUrl(URL foo)
   {
     try
     {
@@ -911,7 +537,8 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
    * @param changeSupport2
    * @return new thread
    */
-  public Thread startDiscoverer(PropertyChangeListener changeSupport2)
+  @Override
+  public FutureTask<WSDiscovererI> startDiscoverer()
   {
     /*    if (restart())
         {
@@ -927,10 +554,9 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     {
       setAborted(true);
     }
-    addPropertyChangeListener(changeSupport2);
-    Thread thr = new Thread(this);
-    thr.start();
-    return thr;
+    FutureTask<WSDiscovererI> task = new FutureTask<>(this, this);
+    new Thread(task).start();
+    return task;
   }
 
   /**
@@ -992,6 +618,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
    * @return a human readable report of any problems with the service URLs used
    *         for discovery
    */
+  @Override
   public String getErrorMessages()
   {
     if (!isRunning() && !isAborted())
@@ -1040,21 +667,22 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     return null;
   }
 
+  @Override
   public int getServerStatusFor(String url)
   {
     if (validServiceUrls != null && validServiceUrls.contains(url))
     {
-      return 1;
+      return STATUS_OK;
     }
     if (urlsWithoutServices != null && urlsWithoutServices.contains(url))
     {
-      return 0;
+      return STATUS_NO_SERVICES;
     }
     if (invalidServiceUrls != null && invalidServiceUrls.contains(url))
     {
-      return -1;
+      return STATUS_INVALID;
     }
-    return -2;
+    return STATUS_UNKNOWN;
   }
 
   /**