JAL-4199 Fix interactive jobs not being stopped
[jalview.git] / src / jalview / ws2 / gui / WebServicesMenuManager.java
index c75c483..5fd4734 100644 (file)
@@ -5,20 +5,21 @@ import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.net.URL;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.TreeMap;
 import java.util.concurrent.CompletionStage;
 
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JLabel;
 import javax.swing.JMenu;
 import javax.swing.JMenuItem;
 import javax.swing.ToolTipManager;
+import javax.swing.border.EmptyBorder;
 
 import jalview.gui.AlignFrame;
 import jalview.gui.Desktop;
@@ -29,7 +30,11 @@ import jalview.viewmodel.AlignmentViewport;
 import jalview.ws.params.ArgumentI;
 import jalview.ws.params.ParamDatastoreI;
 import jalview.ws.params.WsParamSetI;
+import jalview.ws2.actions.BaseTask;
+import jalview.ws2.actions.PollingTaskExecutor;
 import jalview.ws2.actions.alignment.AlignmentAction;
+import jalview.ws2.actions.alignment.AlignmentResult;
+import jalview.ws2.actions.annotation.AlignCalcWorkerAdapter;
 import jalview.ws2.actions.annotation.AnnotationAction;
 import jalview.ws2.actions.api.ActionI;
 import jalview.ws2.actions.api.TaskI;
@@ -102,7 +107,7 @@ public class WebServicesMenuManager
       {
         if (oneshot != null)
           categoryMenu.addSeparator();
-        // addInteractiveEntries(interactive, categoryMenu);
+        addInteractiveEntries(interactive, categoryMenu);
       }
       menu.add(categoryMenu);
     }
@@ -112,9 +117,13 @@ public class WebServicesMenuManager
 
   private void addOneshotEntries(List<WebService<?>> services, JMenu menu)
   {
-    services.sort(Comparator
-        .<WebService<?>, String> comparing(s -> s.getUrl().toString())
-        .thenComparing(WebService::getName));
+    // Workaround. Comparator methods not working in j2s
+    services.sort((ws1, ws2) -> {
+      var res = ws1.getUrl().toString().compareTo(ws2.getUrl().toString());
+      if (res == 0)
+        res = ws1.getName().compareTo(ws2.getName());
+      return res;
+    });
     URL lastHost = null;
     for (WebService<?> service : services)
     {
@@ -137,23 +146,26 @@ public class WebServicesMenuManager
       {
         actionsByCategory
             .computeIfAbsent(
-                Objects.requireNonNullElse(action.getSubcategory(), ""),
+                action.getSubcategory() != null ? action.getSubcategory() : "",
                 k -> new ArrayList<>())
             .add(action);
       }
-      actionsByCategory.forEach((k, v) -> {
+      for (var entry : actionsByCategory.entrySet())
+      {
+        var category = entry.getKey();
+        var actions = entry.getValue();
         // create submenu named {subcategory} with {service} or use root menu
-        var atMenu = k.isEmpty() ? menu : new JMenu(String.format("%s with %s", k, service.getName()));
+        var atMenu = category.isEmpty() ? menu : new JMenu(String.format("%s with %s", category, service.getName()));
         if (atMenu != menu)
           menu.add(atMenu); // add only if submenu
         // sort actions by name pulling nulls to the front
-        v.sort(Comparator.comparing(
+        actions.sort(Comparator.comparing(
             ActionI::getName, Comparator.nullsFirst(Comparator.naturalOrder())));
-        for (ActionI<?> action : v)
+        for (int i = 0; i < actions.size(); i++)
         {
-          addEntriesForAction(action, atMenu, atMenu == menu);
+          addEntriesForAction(actions.get(i), atMenu, atMenu == menu);
         }
-      });
+      }
     }
   }
 
@@ -178,7 +190,7 @@ public class WebServicesMenuManager
     {
       String text = itemName;
       if (datastore.hasParameters() || datastore.hasPresets())
-        text += "with defaults";
+        text += " with defaults";
       JMenuItem item = new JMenuItem(text);
       item.addActionListener(e -> {
         runAction(action, frame.getCurrentView(), Collections.emptyList(),
@@ -238,7 +250,182 @@ public class WebServicesMenuManager
     }
   }
 
-  private TaskI<?> runAction(ActionI<?> action, AlignmentViewport viewport,
+  private void addInteractiveEntries(List<WebService<?>> services, JMenu menu)
+  {
+    Map<String, List<WebService<?>>> byServiceName = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+    for (var service : services)
+    {
+      byServiceName.computeIfAbsent(service.getName(), k -> new ArrayList<>())
+          .add(service);
+    }
+    for (var entry : byServiceName.entrySet())
+    {
+      var group = new InteractiveServiceEntryGroup(entry.getKey(), entry.getValue());
+      group.appendTo(menu);
+    }
+  }
+
+  private class InteractiveServiceEntryGroup
+  {
+    JLabel serviceLabel;
+
+    JMenuItem urlItem = new JMenuItem();
+
+    JCheckBoxMenuItem serviceItem = new JCheckBoxMenuItem();
+
+    JMenuItem editParamsItem = new JMenuItem("Edit parameters...");
+
+    JMenu presetsMenu = new JMenu("Change preset");
+
+    JMenu alternativesMenu = new JMenu("Choose action");
+    {
+      urlItem.setForeground(Color.BLUE);
+      urlItem.setVisible(false);
+      serviceItem.setVisible(false);
+      editParamsItem.setVisible(false);
+      presetsMenu.setVisible(false);
+    }
+
+    InteractiveServiceEntryGroup(String name, List<WebService<?>> services)
+    {
+      serviceLabel = new JLabel(name);
+      serviceLabel.setBorder(new EmptyBorder(0, 6, 0, 6));
+      buildAlternativesMenu(services);
+    }
+
+    private void buildAlternativesMenu(List<WebService<?>> services)
+    {
+      var menu = alternativesMenu;
+      services.sort((ws1, ws2) -> {
+        var res = ws1.getUrl().toString().compareTo(ws2.getUrl().toString());
+        if (res == 0)
+          res = ws1.getName().compareTo(ws2.getName());
+        return res;
+      });
+      URL lastHost = null;
+      for (var service : services)
+      {
+        // Adding url "separator" before each group
+        URL host = service.getUrl();
+        if (!host.equals(lastHost))
+        {
+          if (lastHost != null)
+            menu.addSeparator();
+          var item = new JMenuItem(host.toString());
+          item.setForeground(Color.BLUE);
+          item.addActionListener(e -> Desktop.showUrl(host.toString()));
+          menu.add(item);
+          lastHost = host;
+        }
+        menu.addSeparator();
+        var actionsByCategory = new TreeMap<String, List<ActionI<?>>>();
+        for (ActionI<?> action : service.getActions())
+        {
+          actionsByCategory
+              .computeIfAbsent(
+                  action.getSubcategory() != null ? action.getSubcategory() : "",
+                  k -> new ArrayList<>())
+              .add(action);
+        }
+        actionsByCategory.forEach((key, actions) -> {
+          var atMenu = key.isEmpty() ? menu : new JMenu(key + " with " + service.getName());
+          boolean topLevel = atMenu == menu;
+          if (!topLevel)
+            menu.add(atMenu);
+          actions.sort(Comparator.comparing(
+              a -> a.getName(),
+              Comparator.nullsFirst(Comparator.naturalOrder())));
+          for (ActionI<?> action : actions)
+          {
+            var item = new JMenuItem(action.getFullName());
+            item.addActionListener(e -> setAlternative(action));
+            atMenu.add(item);
+          }
+        });
+      }
+    }
+
+    private void setAlternative(ActionI<?> action)
+    {
+      final var arguments = new ArrayList<ArgumentI>();
+      final WsParamSetI[] lastPreset = { null };
+
+      // update selected url menu item
+      String url = action.getWebService().getUrl().toString();
+      urlItem.setText(url);
+      urlItem.setVisible(true);
+      for (var l : urlItem.getActionListeners())
+        urlItem.removeActionListener(l);
+      urlItem.addActionListener(e -> Desktop.showUrl(url));
+
+      // update selected service menu item
+      serviceItem.setText(action.getFullName());
+      serviceItem.setVisible(true);
+      for (var l : serviceItem.getActionListeners())
+        serviceItem.removeActionListener(l);
+      WebService<?> service = action.getWebService();
+      serviceItem.addActionListener(e -> {
+        runAction(action, frame.getCurrentView(), arguments,
+            Credentials.empty());
+      });
+      serviceItem.setSelected(true);
+
+      // update edit parameters menu item
+      var datastore = service.getParamDatastore();
+      editParamsItem.setVisible(datastore.hasParameters());
+      for (var l : editParamsItem.getActionListeners())
+        editParamsItem.removeActionListener(l);
+      if (datastore.hasParameters())
+      {
+        editParamsItem.addActionListener(e -> {
+          openEditParamsDialog(service.getParamDatastore(), lastPreset[0], arguments)
+              .thenAccept(args -> {
+                if (args != null)
+                {
+                  lastPreset[0] = null;
+                  arguments.clear();
+                  arguments.addAll(args);
+                  runAction(action, frame.getCurrentView(),
+                      arguments, Credentials.empty());
+                }
+              });
+        });
+      }
+
+      // update presets menu
+      presetsMenu.removeAll();
+      presetsMenu.setEnabled(datastore.hasPresets());
+      if (datastore.hasPresets())
+      {
+        for (WsParamSetI preset : datastore.getPresets())
+        {
+          var item = new JMenuItem(preset.getName());
+          item.addActionListener(e -> {
+            lastPreset[0] = preset;
+            runAction(action, frame.getCurrentView(),
+                preset.getArguments(), Credentials.empty());
+          });
+          presetsMenu.add(item);
+        }
+      }
+
+      runAction(action, frame.getCurrentView(), arguments,
+          Credentials.empty());
+    }
+
+    void appendTo(JMenu menu)
+    {
+      menu.add(serviceLabel);
+      menu.add(urlItem);
+      menu.add(serviceItem);
+      menu.add(editParamsItem);
+      menu.add(presetsMenu);
+      menu.add(alternativesMenu);
+    }
+  }
+
+
+  private void runAction(ActionI<?> action, AlignmentViewport viewport,
       List<ArgumentI> args, Credentials credentials)
   {
     // casting and instance checks can be avoided with some effort,
@@ -248,13 +435,35 @@ public class WebServicesMenuManager
       // TODO: test if selection contains enough sequences
       var _action = (AlignmentAction) action;
       var handler = new AlignmentServiceGuiHandler(_action, frame);
-      return _action.perform(viewport, args, credentials, handler);
+      BaseTask<?, AlignmentResult> task = _action.createTask(viewport, args, credentials);
+      var executor = PollingTaskExecutor.fromPool(viewport.getServiceExecutor());
+      task.addTaskEventListener(handler);
+      var future = executor.submit(task);
+      task.setCancelAction(() -> { future.cancel(true); });
+      return;
     }
     if (action instanceof AnnotationAction)
     {
+      var calcManager = viewport.getCalcManager();
+
       var _action = (AnnotationAction) action;
+      var worker = new AlignCalcWorkerAdapter(viewport, frame.alignPanel,
+          _action, args, credentials);
       var handler = new AnnotationServiceGuiHandler(_action, frame);
-      return _action.perform(viewport, args, credentials, handler);
+      worker.addTaskEventListener(handler);
+      for (var w : calcManager.getWorkers())
+      {
+        if (worker.getCalcName() != null && worker.getCalcName().equals(w.getCalcName()))
+        {
+          calcManager.cancelWorker(w);
+          calcManager.removeWorker(w);
+        }
+      }
+      if (action.getWebService().isInteractive())
+        calcManager.registerWorker(worker);
+      else
+        calcManager.startWorker(worker);
+      return;
     }
     throw new IllegalArgumentException(
         String.format("Illegal action type %s", action.getClass().getName()));
@@ -265,7 +474,7 @@ public class WebServicesMenuManager
   {
     final WsJobParameters jobParams;
     if (preset == null && arguments != null && arguments.size() > 0)
-      jobParams = new WsJobParameters(paramStore, preset, arguments);
+      jobParams = new WsJobParameters(paramStore, null, arguments);
     else
       jobParams = new WsJobParameters(paramStore, preset, null);
     if (preset != null)