2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.ws.jws2;
23 import jalview.bin.Cache;
24 import jalview.gui.AlignFrame;
25 import jalview.gui.Desktop;
26 import jalview.gui.JvSwingUtils;
27 import jalview.util.MessageManager;
28 import jalview.ws.WSMenuEntryProviderI;
29 import jalview.ws.api.ServiceWithParameters;
30 import jalview.ws.jws2.jabaws2.Jws2Instance;
31 import jalview.ws.params.ParamDatastoreI;
33 import java.awt.Color;
34 import java.awt.event.ActionEvent;
35 import java.awt.event.ActionListener;
36 import java.beans.PropertyChangeEvent;
37 import java.beans.PropertyChangeListener;
38 import java.beans.PropertyChangeSupport;
39 import java.net.MalformedURLException;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.HashMap;
44 import java.util.HashSet;
45 import java.util.Hashtable;
46 import java.util.List;
49 import java.util.StringTokenizer;
50 import java.util.Vector;
52 import javax.swing.JMenu;
53 import javax.swing.JMenuItem;
55 import compbio.ws.client.Services;
58 * discoverer for jws2 services. Follows the lightweight service discoverer
59 * pattern (archetyped by EnfinEnvision2OneWay)
64 public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
66 public static final String COMPBIO_JABAWS = "http://www.compbio.dundee.ac.uk/jabaws";
69 * the .jalview_properties entry for JWS2 URLS
71 private final static String JWS2HOSTURLS = "JWS2HOSTURLS";
76 private static Jws2Discoverer discoverer;
79 * Override for testing only
81 private static List<String> testUrls = null;
83 // preferred url has precedence over others
84 private String preferredUrl;
86 private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
89 private Vector<String> invalidServiceUrls = null;
91 private Vector<String> urlsWithoutServices = null;
93 private Vector<String> validServiceUrls = null;
95 private volatile boolean running = false;
97 private volatile boolean aborted = false;
99 private Thread oldthread = null;
102 * holds list of services.
104 protected Vector<Jws2Instance> services;
107 * Private constructor enforces use of singleton via getDiscoverer()
109 private Jws2Discoverer()
114 * change listeners are notified of "services" property changes
117 * to be added that consumes new services Hashtable object.
119 public void addPropertyChangeListener(
120 java.beans.PropertyChangeListener listener)
122 changeSupport.addPropertyChangeListener(listener);
131 public void removePropertyChangeListener(
132 java.beans.PropertyChangeListener listener)
134 changeSupport.removePropertyChangeListener(listener);
138 * @return the aborted
140 public boolean isAborted()
149 public void setAborted(boolean aborted)
151 this.aborted = aborted;
158 if (running && oldthread != null && oldthread.isAlive())
169 "Waiting around for old discovery thread to finish.");
170 // wait around until old discoverer dies
172 } catch (Exception e)
177 Cache.log.debug("Old discovery thread has finished.");
181 // first set up exclusion list if needed
182 final Set<String> ignoredServices = new HashSet<>();
183 for (String ignored : Cache
184 .getDefault("IGNORED_JABAWS_SERVICETYPES", "").split("\\|"))
186 ignoredServices.add(ignored);
189 changeSupport.firePropertyChange("services", services,
190 new Vector<Jws2Instance>());
191 oldthread = Thread.currentThread();
194 Class foo = getClass().getClassLoader()
195 .loadClass("compbio.ws.client.Jws2Client");
196 } catch (ClassNotFoundException e)
199 "Not enabling JABA Webservices : client jar is not available."
200 + "\nPlease check that your webstart JNLP file is up to date!");
204 // reinitialise records of good and bad service URLs
205 if (services != null)
207 services.removeAllElements();
209 if (urlsWithoutServices != null)
211 urlsWithoutServices.removeAllElements();
213 if (invalidServiceUrls != null)
215 invalidServiceUrls.removeAllElements();
217 if (validServiceUrls != null)
219 validServiceUrls.removeAllElements();
221 ArrayList<String> svctypes = new ArrayList<>();
223 List<JabaWsServerQuery> qrys = new ArrayList<>();
224 for (final String jwsserver : getServiceUrls())
226 JabaWsServerQuery squery = new JabaWsServerQuery(this, jwsserver);
227 if (svctypes.size() == 0)
229 // TODO: remove this ugly hack to get Canonical JABA service ordering
230 // for all possible services
231 for (Services sv : squery.JABAWS2SERVERS)
233 if (!ignoredServices.contains(sv.toString()))
235 svctypes.add(sv.toString());
241 new Thread(squery).start();
243 boolean finished = true;
250 } catch (Exception e)
254 for (JabaWsServerQuery squery : qrys)
256 if (squery.isRunning())
264 "Aborting " + qrys.size() + " JABAWS discovery threads.");
265 for (JabaWsServerQuery squery : qrys)
267 squery.setQuit(true);
270 } while (!aborted && !finished);
273 // resort services according to order found in jabaws service list
274 // also ensure services for each host are ordered in same way.
276 if (services != null && services.size() > 0)
278 Jws2Instance[] svcs = new Jws2Instance[services.size()];
279 int[] spos = new int[services.size()];
281 List<String> svcUrls = getServiceUrls();
282 for (Jws2Instance svc : services)
285 spos[ipos++] = 1000 * svcUrls.indexOf(svc.getHostURL()) + 1
286 + svctypes.indexOf(svc.getName());
288 jalview.util.QuickSort.sort(spos, svcs);
289 services = new Vector<>();
290 for (Jws2Instance svc : svcs)
292 if (!ignoredServices.contains(svc.getName()))
301 changeSupport.firePropertyChange("services", new Vector<Jws2Instance>(),
306 * record this service endpoint so we can use it
312 synchronized void addService(String jwsservers, Jws2Instance service)
314 if (services == null)
316 services = new Vector<>();
319 "Discovered service: " + jwsservers + " " + service.toString());
320 // Jws2Instance service = new Jws2Instance(jwsservers, srv.toString(),
323 services.add(service);
324 // retrieve the presets and parameter set and cache now
325 ParamDatastoreI pds = service.getParamStore();
330 service.hasParameters();
331 if (validServiceUrls == null)
333 validServiceUrls = new Vector<>();
335 validServiceUrls.add(jwsservers);
339 * attach all available web services to the appropriate submenu in the given
343 public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
345 // dynamically regenerate service list.
346 populateWSMenuEntry(wsmenu, alignFrame, null);
349 private boolean isRecalculable(String action)
351 return (action != null && action.equalsIgnoreCase("conservation"));
354 private void populateWSMenuEntry(JMenu jws2al,
355 final AlignFrame alignFrame, String typeFilter)
357 if (running || services == null || services.size() == 0)
363 * eventually, JWS2 services will appear under the same align/etc submenus.
364 * for moment we keep them separate.
367 List<Jws2Instance> enumerableServices = new ArrayList<>();
368 // jws2al.removeAll();
369 Map<String, ServiceWithParameters> preferredHosts = new HashMap<>();
370 Map<String, List<ServiceWithParameters>> alternates = new HashMap<>();
371 for (Jws2Instance service : services.toArray(new Jws2Instance[0]))
373 // TODO: check this behaves with refactored serviceType to getName
374 if (!isRecalculable(service.getName()))
376 // add 'one shot' services to be displayed using the classic menu
378 enumerableServices.add(service);
382 if (!preferredHosts.containsKey(service.getName()))
384 ServiceWithParameters preferredInstance = getPreferredServiceFor(
385 alignFrame, service.getName());
386 if (preferredInstance != null)
388 preferredHosts.put(service.getName(), preferredInstance);
392 preferredHosts.put(service.getName(), service);
395 List<ServiceWithParameters> ph = alternates.get(service.getName());
396 if (preferredHosts.get(service.getName()) != service)
400 ph = new ArrayList<>();
403 alternates.put(service.getName(), ph);
409 // create GUI element for classic services
410 addEnumeratedServices(jws2al, alignFrame, enumerableServices);
411 // and the instantaneous services
412 for (final ServiceWithParameters service : preferredHosts.values())
414 atpoint = JvSwingUtils.findOrCreateMenu(jws2al,
415 service.getServiceType());
417 if (atpoint.getItemCount() > 1)
419 // previous service of this type already present
420 atpoint.addSeparator();
422 atpoint.add(hitm = new JMenuItem(service.getHostURL()));
423 hitm.setForeground(Color.blue);
424 hitm.addActionListener(new ActionListener()
428 public void actionPerformed(ActionEvent e)
430 Desktop.showUrl(service.getHostURL());
433 hitm.setToolTipText(JvSwingUtils.wrapTooltip(false,
434 MessageManager.getString("label.open_jabaws_web_page")));
436 service.attachWSMenuEntry(atpoint, alignFrame);
437 if (alternates.containsKey(service.getName()))
439 atpoint.add(hitm = new JMenu(
440 MessageManager.getString("label.switch_server")));
441 hitm.setToolTipText(JvSwingUtils.wrapTooltip(false,
442 MessageManager.getString("label.choose_jabaws_server")));
443 for (final ServiceWithParameters sv : alternates.get(service.getName()))
446 hitm.add(itm = new JMenuItem(sv.getHostURL()));
447 itm.setForeground(Color.blue);
448 itm.addActionListener(new ActionListener()
452 public void actionPerformed(ActionEvent arg0)
454 new Thread(new Runnable()
459 setPreferredServiceFor(alignFrame, sv.getName(),
460 sv.getServiceType(), sv);
461 changeSupport.firePropertyChange("services",
462 new Vector<Jws2Instance>(), services);
474 * add services using the Java 2.5/2.6/2.7 system which optionally creates
475 * submenus to index by host and service program type
477 private void addEnumeratedServices(final JMenu jws2al,
478 final AlignFrame alignFrame,
479 List<Jws2Instance> enumerableServices)
481 boolean byhost = Cache.getDefault("WSMENU_BYHOST", false),
482 bytype = Cache.getDefault("WSMENU_BYTYPE", false);
484 * eventually, JWS2 services will appear under the same align/etc submenus.
485 * for moment we keep them separate.
489 List<String> hostLabels = new ArrayList<>();
490 Hashtable<String, String> lasthostFor = new Hashtable<>();
491 Hashtable<String, ArrayList<Jws2Instance>> hosts = new Hashtable<>();
492 ArrayList<String> hostlist = new ArrayList<>();
493 for (Jws2Instance service : enumerableServices)
495 ArrayList<Jws2Instance> hostservices = hosts
496 .get(service.getHostURL());
497 if (hostservices == null)
499 hosts.put(service.getHostURL(),
500 hostservices = new ArrayList<>());
501 hostlist.add(service.getHostURL());
503 hostservices.add(service);
505 // now add hosts in order of the given array
506 for (String host : hostlist)
508 Jws2Instance orderedsvcs[] = hosts.get(host)
509 .toArray(new Jws2Instance[1]);
510 String sortbytype[] = new String[orderedsvcs.length];
511 for (int i = 0; i < sortbytype.length; i++)
513 sortbytype[i] = orderedsvcs[i].getName();
515 jalview.util.QuickSort.sort(sortbytype, orderedsvcs);
516 for (final Jws2Instance service : orderedsvcs)
518 atpoint = JvSwingUtils.findOrCreateMenu(jws2al,
519 service.getAction());
520 String type = service.getName();
523 atpoint = JvSwingUtils.findOrCreateMenu(atpoint, host);
524 if (atpoint.getToolTipText() == null)
526 atpoint.setToolTipText(MessageManager
527 .formatMessage("label.services_at", new String[]
533 atpoint = JvSwingUtils.findOrCreateMenu(atpoint, type);
534 if (atpoint.getToolTipText() == null)
536 atpoint.setToolTipText(service.getActionText());
539 if (!byhost && !hostLabels.contains(
540 host + service.getName() + service.getActionText()))
541 // !hostLabels.contains(host + (bytype ?
542 // service.serviceType+service.getActionText() : "")))
544 // add a marker indicating where this service is hosted
545 // relies on services from the same host being listed in a
549 if (hostLabels.contains(host))
551 atpoint.addSeparator();
555 hostLabels.add(host);
557 if (lasthostFor.get(service.getAction()) == null
558 || !lasthostFor.get(service.getAction()).equals(host))
560 atpoint.add(hitm = new JMenuItem(host));
561 hitm.setForeground(Color.blue);
562 hitm.addActionListener(new ActionListener()
566 public void actionPerformed(ActionEvent e)
568 Desktop.showUrl(service.getHostURL());
572 JvSwingUtils.wrapTooltip(true, MessageManager
573 .getString("label.open_jabaws_web_page")));
574 lasthostFor.put(service.getAction(), host);
577 host + service.getName() + service.getActionText());
580 service.attachWSMenuEntry(atpoint, alignFrame);
585 public static void main(String[] args)
589 testUrls = new ArrayList<>();
590 for (String url : args)
596 Thread runner = getDiscoverer()
597 .startDiscoverer(new PropertyChangeListener()
601 public void propertyChange(PropertyChangeEvent evt)
603 if (getDiscoverer().services != null)
605 System.out.println("Changesupport: There are now "
606 + getDiscoverer().services.size() + " services");
608 for (ServiceWithParameters instance : getDiscoverer().services)
610 System.out.println("Service " + i++ + " "
611 + instance.getClass() + "@"
612 + instance.getHostURL()
613 + ": " + instance.getActionText());
619 while (runner.isAlive())
624 } catch (InterruptedException e)
632 } catch (InterruptedException x)
638 * Returns the singleton instance of this class.
642 public static Jws2Discoverer getDiscoverer()
644 if (discoverer == null)
646 discoverer = new Jws2Discoverer();
651 public boolean hasServices()
653 return !running && services != null && services.size() > 0;
656 public boolean isRunning()
661 public void setServiceUrls(List<String> wsUrls)
663 if (wsUrls != null && !wsUrls.isEmpty())
665 StringBuilder urls = new StringBuilder(128);
667 for (String url : wsUrls)
673 Cache.setProperty(JWS2HOSTURLS, urls.toString());
677 Cache.removeProperty(JWS2HOSTURLS);
682 * Returns web service URLs, in the order in which they should be tried (or an
687 public List<String> getServiceUrls()
689 if (testUrls != null)
691 // return test urls, if there are any, instead of touching cache
694 List<String> urls = new ArrayList<>();
696 if (this.preferredUrl != null)
698 urls.add(preferredUrl);
701 String surls = Cache.getDefault(JWS2HOSTURLS, COMPBIO_JABAWS);
704 StringTokenizer st = new StringTokenizer(surls, ",");
705 while (st.hasMoreElements())
710 url = st.nextToken();
712 if (!urls.contains(url))
718 Cache.log.warn("Ignoring duplicate url " + url + " in "
719 + JWS2HOSTURLS + " list");
721 } catch (MalformedURLException ex)
723 Cache.log.warn("Problem whilst trying to make a URL from '"
724 + ((url != null) ? url : "<null>") + "'");
726 "This was probably due to a malformed comma separated list"
727 + " in the " + JWS2HOSTURLS
728 + " entry of $(HOME)/.jalview_properties)");
729 Cache.log.debug("Exception was ", ex);
732 } catch (Exception ex)
734 Cache.log.warn("Error parsing comma separated list of urls in "
735 + JWS2HOSTURLS + " preference.", ex);
740 public Vector<ServiceWithParameters> getServices()
742 return (services == null) ? new Vector<>()
743 : new Vector<>(services);
747 * test the given URL with the JabaWS test code
752 public static boolean testServiceUrl(URL foo)
756 compbio.ws.client.WSTester
758 { "-h=" + foo.toString() });
759 } catch (Exception e)
763 } catch (OutOfMemoryError e)
776 public boolean restart()
793 * Start a fresh discovery thread and notify the given object when we're
794 * finished. Any known existing threads will be killed before this one is
797 * @param changeSupport2
800 public Thread startDiscoverer(PropertyChangeListener changeSupport2)
808 Thread thr = new Thread(this);
816 addPropertyChangeListener(changeSupport2);
817 Thread thr = new Thread(this);
823 * @return the invalidServiceUrls
825 public Vector<String> getInvalidServiceUrls()
827 return invalidServiceUrls;
831 * @return the urlsWithoutServices
833 public Vector<String> getUrlsWithoutServices()
835 return urlsWithoutServices;
839 * add an 'empty' JABA server to the list. Only servers not already in the
840 * 'bad URL' list will be added to this list.
844 public synchronized void addUrlwithnoservices(String jwsservers)
846 if (urlsWithoutServices == null)
848 urlsWithoutServices = new Vector<>();
851 if ((invalidServiceUrls == null
852 || !invalidServiceUrls.contains(jwsservers))
853 && !urlsWithoutServices.contains(jwsservers))
855 urlsWithoutServices.add(jwsservers);
860 * add a bad URL to the list
864 public synchronized void addInvalidServiceUrl(String jwsservers)
866 if (invalidServiceUrls == null)
868 invalidServiceUrls = new Vector<>();
870 if (!invalidServiceUrls.contains(jwsservers))
872 invalidServiceUrls.add(jwsservers);
878 * @return a human readable report of any problems with the service URLs used
881 public String getErrorMessages()
883 if (!isRunning() && !isAborted())
885 StringBuffer ermsg = new StringBuffer();
886 boolean list = false;
887 if (getInvalidServiceUrls() != null
888 && getInvalidServiceUrls().size() > 0)
890 ermsg.append(MessageManager.getString("warn.urls_not_contacted")
892 for (String svcurl : getInvalidServiceUrls())
899 ermsg.append(svcurl);
901 ermsg.append("\n\n");
904 if (getUrlsWithoutServices() != null
905 && getUrlsWithoutServices().size() > 0)
908 MessageManager.getString("warn.urls_no_jaba") + ": \n");
909 for (String svcurl : getUrlsWithoutServices())
916 ermsg.append(svcurl);
920 if (ermsg.length() > 1)
922 return ermsg.toString();
929 public int getServerStatusFor(String url)
931 if (validServiceUrls != null && validServiceUrls.contains(url))
935 if (urlsWithoutServices != null && urlsWithoutServices.contains(url))
939 if (invalidServiceUrls != null && invalidServiceUrls.contains(url))
947 * pick the user's preferred service based on a set of URLs (jaba server
948 * locations) and service URIs (specifying version and service interface
952 * @return null or best match for given uri/ls.
954 public ServiceWithParameters getPreferredServiceFor(String[] serviceURLs)
956 HashSet<String> urls = new HashSet<>();
957 urls.addAll(Arrays.asList(serviceURLs));
958 ServiceWithParameters match = null;
959 List<ServiceWithParameters> ourServices = getServices();
961 if (ourServices != null)
963 for (ServiceWithParameters svc : services)
965 // TODO getNameURI Should return a versioned URI for the service, but
966 // doesn't as of 2.11
967 if (urls.contains(svc.getNameURI()))
971 // for moment we always pick service from server ordered first in
972 // user's preferences
975 if (urls.contains(svc.getUri()))
977 // stop and return - we've matched type URI and URI for service
987 Map<String, Map<String, String>> preferredServiceMap = new HashMap<>();
990 * get current preferred endpoint of the given Jabaws service, or global
994 * null or a specific alignFrame
996 * Jws2Instance.getName() for service
997 * @return null if no service of this type is available, the preferred service
998 * for the serviceType and af if specified and if defined.
1000 public ServiceWithParameters getPreferredServiceFor(AlignFrame af,
1003 String serviceurl = null;
1004 synchronized (preferredServiceMap)
1006 String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
1007 Map<String, String> prefmap = preferredServiceMap.get(afid);
1008 if (afid.length() > 0 && prefmap == null)
1010 // recover global setting, if any
1011 prefmap = preferredServiceMap.get("");
1013 if (prefmap != null)
1015 serviceurl = prefmap.get(serviceName);
1019 Jws2Instance response = null;
1020 for (Jws2Instance svc : services)
1022 if (svc.getName().equals(serviceName))
1024 if (serviceurl == null || serviceurl.equals(svc.getHostURL()))
1034 public void setPreferredServiceFor(AlignFrame af, String serviceName,
1035 String serviceAction, ServiceWithParameters selectedServer)
1037 // TODO: pull out and generalise for the selectedServer's attributes
1038 String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
1039 if (preferredServiceMap == null)
1041 preferredServiceMap = new HashMap<>();
1043 Map<String, String> prefmap = preferredServiceMap.get(afid);
1044 if (prefmap == null)
1046 prefmap = new HashMap<>();
1047 preferredServiceMap.put(afid, prefmap);
1049 prefmap.put(serviceName, selectedServer.getHostURL());
1050 prefmap.put(serviceAction, selectedServer.getHostURL());
1053 public void setPreferredServiceFor(String serviceType,
1054 String serviceAction, ServiceWithParameters selectedServer)
1056 setPreferredServiceFor(null, serviceType, serviceAction,
1061 * Set a URL to try before any others. For use with command-line parameter to
1062 * configure a local Jabaws installation without the need to add to property
1066 * @throws MalformedURLException
1068 public void setPreferredUrl(String value) throws MalformedURLException
1070 if (value != null && value.trim().length() > 0)
1073 preferredUrl = value;