JAL-3690 alternative servers menu fix
[jalview.git] / src / jalview / ws / jws2 / PreferredServiceRegistry.java
1 package jalview.ws.jws2;
2
3 import jalview.bin.Cache;
4 import jalview.gui.AlignFrame;
5 import jalview.gui.Desktop;
6 import jalview.gui.JvSwingUtils;
7 import jalview.util.MessageManager;
8 import jalview.ws.api.ServiceWithParameters;
9
10 import java.awt.Color;
11 import java.awt.event.ActionEvent;
12 import java.awt.event.ActionListener;
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.Hashtable;
18 import java.util.List;
19 import java.util.Map;
20 import javax.swing.JMenu;
21 import javax.swing.JMenuItem;
22
23 public class PreferredServiceRegistry
24 {
25
26   private static PreferredServiceRegistry us = new PreferredServiceRegistry();
27
28   public static PreferredServiceRegistry getRegistry()
29   {
30     if (us == null)
31     {
32       us = new PreferredServiceRegistry();
33     }
34     return us;
35   }
36
37   List<ServiceWithParameters> ourServices = new ArrayList<>();
38
39   /**
40    * forget about any known services
41    */
42   public void clearServices()
43   {
44     ourServices.clear();
45   }
46
47   public void populateWSMenuEntry(List<ServiceWithParameters> services,
48           PreferredServiceChangeListener changeListener, JMenu menu,
49           final AlignFrame alignFrame, String typeFilter)
50   {
51     /**
52      * eventually, JWS2 services will appear under the same align/etc submenus.
53      * for moment we keep them separate.
54      */
55     ourServices.addAll(services);
56     JMenu atpoint;
57     
58     List<ServiceWithParameters> oneshotServices = new ArrayList<>();
59     List<ServiceWithParameters> interactiveServices = new ArrayList<>();
60     Map<String, ServiceWithParameters> preferredHosts = new HashMap<>();
61     Map<String, List<ServiceWithParameters>> alternates = new HashMap<>();
62     
63     for (var service : services) 
64     {
65       if (service.isInteractiveUpdate())
66         interactiveServices.add(service);
67       else
68         oneshotServices.add(service);
69     }
70     for (var service : interactiveServices)
71     {
72       if (!preferredHosts.containsKey(service.getName()))
73       {
74         var preferred = getPreferredServiceFor(alignFrame, service.getName());
75         preferredHosts.put(service.getName(), (preferred != null) ? preferred : service);
76       }
77       var ph = alternates.getOrDefault(service.getName(), new ArrayList<>());
78       if (!preferredHosts.containsValue(service)) 
79       {
80         ph.add(service);
81         alternates.putIfAbsent(service.getName(), ph);
82       }
83     }
84
85     // create GUI element for classic services
86     addEnumeratedServices(menu, alignFrame, oneshotServices);
87     // and the instantaneous services
88     for (final ServiceWithParameters service : preferredHosts.values())
89     {
90       atpoint = JvSwingUtils.findOrCreateMenu(menu,
91               service.getServiceType());
92       if (atpoint.getItemCount() > 1)
93       {
94         // previous service of this type already present
95         atpoint.addSeparator();
96       }
97       JMenuItem hitm;
98       atpoint.add(hitm = new JMenuItem(service.getHostURL()));
99       hitm.setForeground(Color.blue);
100       hitm.addActionListener(e -> Desktop.showUrl(service.getHostURL()));
101       hitm.setToolTipText(JvSwingUtils.wrapTooltip(false,
102               MessageManager.getString("label.open_jabaws_web_page")));
103
104       service.attachWSMenuEntry(atpoint, alignFrame);
105       if (alternates.containsKey(service.getName()))
106       {
107         atpoint.add(hitm = new JMenu(
108                 MessageManager.getString("label.switch_server")));
109         hitm.setToolTipText(JvSwingUtils.wrapTooltip(false,
110                 MessageManager.getString("label.choose_jabaws_server")));
111         for (final ServiceWithParameters sv : alternates
112                 .get(service.getName()))
113         {
114           JMenuItem itm;
115           hitm.add(itm = new JMenuItem(sv.getHostURL()));
116           itm.setForeground(Color.blue);
117           itm.addActionListener(e -> {
118             setPreferredServiceFor(alignFrame, sv.getName(), sv.getServiceType(), sv);
119             changeListener.preferredServiceChanged(sv);
120           });
121         }
122       }
123     }
124   }
125
126   /**
127    * add services using the Java 2.5/2.6/2.7 system which optionally creates
128    * submenus to index by host and service program type
129    */
130   private void addEnumeratedServices(final JMenu jws2al,
131           final AlignFrame alignFrame,
132           List<ServiceWithParameters> enumerableServices)
133   {
134     boolean byhost = Cache.getDefault("WSMENU_BYHOST", false),
135             bytype = Cache.getDefault("WSMENU_BYTYPE", false);
136     /**
137      * eventually, JWS2 services will appear under the same align/etc submenus.
138      * for moment we keep them separate.
139      */
140     JMenu atpoint;
141
142     List<String> hostLabels = new ArrayList<>();
143     Hashtable<String, String> lasthostFor = new Hashtable<>();
144     Hashtable<String, ArrayList<ServiceWithParameters>> hosts = new Hashtable<>();
145     ArrayList<String> hostlist = new ArrayList<>();
146     for (ServiceWithParameters service : enumerableServices)
147     {
148       ArrayList<ServiceWithParameters> hostservices = hosts
149               .get(service.getHostURL());
150       if (hostservices == null)
151       {
152         hosts.put(service.getHostURL(), hostservices = new ArrayList<>());
153         hostlist.add(service.getHostURL());
154       }
155       hostservices.add(service);
156     }
157     // now add hosts in order of the given array
158     for (String host : hostlist)
159     {
160       ServiceWithParameters orderedsvcs[] = hosts.get(host)
161               .toArray(new ServiceWithParameters[1]);
162       String sortbytype[] = new String[orderedsvcs.length];
163       for (int i = 0; i < sortbytype.length; i++)
164       {
165         sortbytype[i] = orderedsvcs[i].getName();
166       }
167       jalview.util.QuickSort.sort(sortbytype, orderedsvcs);
168       for (final ServiceWithParameters service : orderedsvcs)
169       {
170         atpoint = JvSwingUtils.findOrCreateMenu(jws2al,
171                 service.getAction());
172         String type = service.getName();
173         if (byhost)
174         {
175           atpoint = JvSwingUtils.findOrCreateMenu(atpoint, host);
176           if (atpoint.getToolTipText() == null)
177           {
178             atpoint.setToolTipText(MessageManager
179                     .formatMessage("label.services_at", new String[]
180                     { host }));
181           }
182         }
183         if (bytype)
184         {
185           atpoint = JvSwingUtils.findOrCreateMenu(atpoint, type);
186           if (atpoint.getToolTipText() == null)
187           {
188             atpoint.setToolTipText(service.getActionText());
189           }
190         }
191         if (!byhost && !hostLabels.contains(
192                 host + service.getName() + service.getActionText()))
193         // !hostLabels.contains(host + (bytype ?
194         // service.serviceType+service.getActionText() : "")))
195         {
196           // add a marker indicating where this service is hosted
197           // relies on services from the same host being listed in a
198           // contiguous
199           // group
200           JMenuItem hitm;
201           if (hostLabels.contains(host))
202           {
203             atpoint.addSeparator();
204           }
205           else
206           {
207             hostLabels.add(host);
208           }
209           if (lasthostFor.get(service.getAction()) == null
210                   || !lasthostFor.get(service.getAction()).equals(host))
211           {
212             atpoint.add(hitm = new JMenuItem(host));
213             hitm.setForeground(Color.blue);
214             hitm.addActionListener(new ActionListener()
215             {
216
217               @Override
218               public void actionPerformed(ActionEvent e)
219               {
220                 Desktop.showUrl(service.getHostURL());
221               }
222             });
223             hitm.setToolTipText(
224                     JvSwingUtils.wrapTooltip(true, MessageManager
225                             .getString("label.open_jabaws_web_page")));
226             lasthostFor.put(service.getAction(), host);
227           }
228           hostLabels
229                   .add(host + service.getName() + service.getActionText());
230         }
231
232         service.attachWSMenuEntry(atpoint, alignFrame);
233       }
234     }
235   }
236
237   /**
238    * pick the user's preferred service based on a set of URLs (jaba server
239    * locations) and service URIs (specifying version and service interface
240    * class)
241    * 
242    * @param serviceURL
243    * @return null or best match for given uri/ls.
244    */
245   public ServiceWithParameters getPreferredServiceFor(String[] serviceURLs)
246   {
247     HashSet<String> urls = new HashSet<>();
248     urls.addAll(Arrays.asList(serviceURLs));
249     ServiceWithParameters match = null;
250
251     if (ourServices != null)
252     {
253       for (ServiceWithParameters svc : ourServices)
254       {
255         // TODO getNameURI Should return a versioned URI for the service, but
256         // doesn't as of 2.11
257         if (urls.contains(svc.getNameURI()))
258         {
259           if (match == null)
260           {
261             // for moment we always pick service from server ordered first in
262             // user's preferences
263             match = svc;
264           }
265           if (urls.contains(svc.getUri()))
266           {
267             // stop and return - we've matched type URI and URI for service
268             // endpoint
269             return svc;
270           }
271         }
272       }
273     }
274     return match;
275   }
276
277   Map<String, Map<String, String>> preferredServiceMap = new HashMap<>();
278
279   /**
280    * get current preferred endpoint of the given Jabaws service, or global
281    * default
282    * 
283    * @param af
284    *          null or a specific alignFrame
285    * @param serviceName
286    *          ServiceWithParameters.getName() for service
287    * @return null if no service of this type is available, the preferred service
288    *         for the serviceType and af if specified and if defined.
289    */
290   public ServiceWithParameters getPreferredServiceFor(AlignFrame af,
291           String serviceName)
292   {
293     String serviceurl = null;
294     synchronized (preferredServiceMap)
295     {
296       String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
297       Map<String, String> prefmap = preferredServiceMap.get(afid);
298       if (afid.length() > 0 && prefmap == null)
299       {
300         // recover global setting, if any
301         prefmap = preferredServiceMap.get("");
302       }
303       if (prefmap != null)
304       {
305         serviceurl = prefmap.get(serviceName);
306       }
307
308     }
309     ServiceWithParameters response = null;
310     for (ServiceWithParameters svc : ourServices)
311     {
312       if (svc.getName().equals(serviceName))
313       {
314         if (serviceurl == null || serviceurl.equals(svc.getHostURL()))
315         {
316           response = svc;
317           break;
318         }
319       }
320     }
321     return response;
322   }
323
324   public void setPreferredServiceFor(AlignFrame af, String serviceName,
325           String serviceAction, ServiceWithParameters selectedServer)
326   {
327     // TODO: pull out and generalise for the selectedServer's attributes
328     String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
329     if (preferredServiceMap == null)
330     {
331       preferredServiceMap = new HashMap<>();
332     }
333     Map<String, String> prefmap = preferredServiceMap.get(afid);
334     if (prefmap == null)
335     {
336       prefmap = new HashMap<>();
337       preferredServiceMap.put(afid, prefmap);
338     }
339     prefmap.put(serviceName, selectedServer.getHostURL());
340     prefmap.put(serviceAction, selectedServer.getHostURL());
341   }
342
343   public void setPreferredServiceFor(String serviceType,
344           String serviceAction, ServiceWithParameters selectedServer)
345   {
346     setPreferredServiceFor(null, serviceType, serviceAction,
347             selectedServer);
348   }
349
350   public boolean contains(ServiceWithParameters service)
351   {
352     return ourServices.contains(service);
353   }
354
355 }