2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3 * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
11 * Jalview is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty
13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14 * PURPOSE. See the GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along with Jalview. If not, see <http://www.gnu.org/licenses/>.
18 package jalview.ws.jws2;
20 import jalview.bin.Cache;
21 import jalview.gui.AlignFrame;
22 import jalview.gui.Desktop;
23 import jalview.gui.JvSwingUtils;
24 import jalview.ws.WSMenuEntryProviderI;
25 import jalview.ws.jws2.jabaws2.Jws2Instance;
27 import java.awt.Color;
28 import java.awt.event.ActionEvent;
29 import java.awt.event.ActionListener;
30 import java.beans.PropertyChangeEvent;
31 import java.beans.PropertyChangeListener;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.HashMap;
36 import java.util.HashSet;
37 import java.util.Hashtable;
38 import java.util.List;
40 import java.util.StringTokenizer;
41 import java.util.Vector;
43 import javax.swing.JMenu;
44 import javax.swing.JMenuItem;
46 import compbio.ws.client.Services;
49 * discoverer for jws2 services. Follows the lightweight service discoverer
50 * pattern (archetyped by EnfinEnvision2OneWay)
55 public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
57 private java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(
61 * change listeners are notified of "services" property changes
64 * to be added that consumes new services Hashtable object.
66 public void addPropertyChangeListener(
67 java.beans.PropertyChangeListener listener)
69 changeSupport.addPropertyChangeListener(listener);
78 public void removePropertyChangeListener(
79 java.beans.PropertyChangeListener listener)
81 changeSupport.removePropertyChangeListener(listener);
84 boolean running = false, aborted = false;
89 public boolean isAborted()
98 public void setAborted(boolean aborted)
100 this.aborted = aborted;
103 Thread oldthread = null;
107 if (running && oldthread != null && oldthread.isAlive())
118 .debug("Waiting around for old discovery thread to finish.");
119 // wait around until old discoverer dies
121 } catch (Exception e)
125 Cache.log.debug("Old discovery thread has finished.");
128 changeSupport.firePropertyChange("services", services, new Vector());
129 oldthread = Thread.currentThread();
132 Class foo = getClass().getClassLoader().loadClass(
133 "compbio.ws.client.Jws2Client");
134 } catch (ClassNotFoundException e)
137 .println("Not enabling JABA Webservices : client jar is not available."
138 + "\nPlease check that your webstart JNLP file is up to date!");
142 // reinitialise records of good and bad service URLs
143 if (services != null)
145 services.removeAllElements();
147 if (urlsWithoutServices != null)
149 urlsWithoutServices.removeAllElements();
151 if (invalidServiceUrls != null)
153 invalidServiceUrls.removeAllElements();
155 if (validServiceUrls != null)
157 validServiceUrls.removeAllElements();
159 ArrayList<String> svctypes=new ArrayList<String>();
161 List<JabaWsServerQuery> qrys = new ArrayList<JabaWsServerQuery>();
162 for (final String jwsservers : getServiceUrls())
164 JabaWsServerQuery squery = new JabaWsServerQuery(this, jwsservers);
165 if (svctypes.size()==0)
167 // TODO: remove this ugly hack to get Canonical JABA service ordering for all possible services
168 for (Services sv:squery.JABAWS2SERVERS)
170 svctypes.add(sv.toString());
175 new Thread(squery).start();
177 boolean finished = true;
184 } catch (Exception e)
188 for (JabaWsServerQuery squery : qrys)
190 finished = finished && !squery.isRunning();
194 Cache.log.debug("Aborting " + qrys.size()
195 + " JABAWS discovery threads.");
196 for (JabaWsServerQuery squery : qrys)
198 squery.setQuit(true);
201 } while (!aborted && !finished);
204 // resort services according to order found in jabaws service list
205 // also ensure servics for each host are ordered in same way.
207 if (services!=null && services.size()>0)
209 Jws2Instance[] svcs=new Jws2Instance[services.size()];
210 int[] spos=new int[services.size()];
212 Vector svcUrls = getServiceUrls();
213 for (Jws2Instance svc:services)
216 spos[ipos++]=1000*svcUrls.indexOf(svc.getHost()) + 1+svctypes.indexOf(svc.serviceType);
218 jalview.util.QuickSort.sort(spos, svcs);
219 services=new Vector<Jws2Instance>();
220 for (Jws2Instance svc:svcs) {
227 changeSupport.firePropertyChange("services", new Vector(), services);
231 * record this service endpoint so we can use it
237 synchronized void addService(String jwsservers, Jws2Instance service)
239 if (services == null)
241 services = new Vector<Jws2Instance>();
243 System.out.println("Discovered service: " + jwsservers + " "
244 + service.toString());
245 // Jws2Instance service = new Jws2Instance(jwsservers, srv.toString(),
248 services.add(service);
249 // retrieve the presets and parameter set and cache now
250 service.getParamStore().getPresets();
251 service.hasParameters();
252 if (validServiceUrls==null)
254 validServiceUrls=new Vector();
256 validServiceUrls.add(jwsservers);
260 * holds list of services.
262 protected Vector<Jws2Instance> services;
264 * attach all available web services to the appropriate submenu in the given JMenu
266 public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
268 // dynamically regenerate service list.
269 populateWSMenuEntry(wsmenu, alignFrame, null);
271 private boolean isRecalculable(String action) {
272 return (action!=null && action.equalsIgnoreCase("conservation"));
274 private void populateWSMenuEntry(JMenu jws2al, final AlignFrame alignFrame, String typeFilter)
276 if (running || services == null || services.size() == 0)
280 boolean byhost = Cache.getDefault("WSMENU_BYHOST", false), bytype = Cache
281 .getDefault("WSMENU_BYTYPE", false);
283 * eventually, JWS2 services will appear under the same align/etc submenus.
284 * for moment we keep them separate.
287 List<Jws2Instance> enumerableServices=new ArrayList<Jws2Instance>();
288 //jws2al.removeAll();
289 Map<String, Jws2Instance> preferredHosts=new HashMap<String,Jws2Instance>();
290 Map<String, List<Jws2Instance>> alternates = new HashMap<String, List<Jws2Instance>>();
291 for (Jws2Instance service : services.toArray(new Jws2Instance[0]))
293 if (!isRecalculable(service.action)) {
294 // add 'one shot' services to be displayed using the classic menu structure
295 enumerableServices.add(service);
297 if (!preferredHosts.containsKey(service.serviceType))
299 Jws2Instance preferredInstance = getPreferredServiceFor(alignFrame,
300 service.serviceType);
301 if (preferredInstance != null)
303 preferredHosts.put(service.serviceType, preferredInstance);
307 preferredHosts.put(service.serviceType, service);
310 List<Jws2Instance> ph=alternates.get(service.serviceType);
311 if (preferredHosts.get(service.serviceType)!=service)
315 ph=new ArrayList<Jws2Instance>();
318 alternates.put(service.serviceType, ph);
324 // create GUI element for classic services
325 addEnumeratedServices(jws2al, alignFrame, enumerableServices);
326 // and the instantaneous services
327 for (final Jws2Instance service : preferredHosts.values())
329 atpoint = JvSwingUtils.findOrCreateMenu(jws2al,service.action);
331 if (atpoint.getItemCount()>1) {
332 // previous service of this type already present
333 atpoint.addSeparator();
335 atpoint.add(hitm = new JMenuItem(service.getHost()));
336 hitm.setForeground(Color.blue);
337 hitm.addActionListener(new ActionListener()
341 public void actionPerformed(ActionEvent e)
343 Desktop.showUrl(service.getHost());
346 hitm.setToolTipText(JvSwingUtils
347 .wrapTooltip("Opens the JABAWS server's homepage in web browser"));
349 service.attachWSMenuEntry(atpoint, alignFrame);
350 if (alternates.containsKey(service.serviceType))
352 atpoint.add(hitm=new JMenu("Switch server"));
353 hitm.setToolTipText(JvSwingUtils.wrapTooltip("Choose a server for running this service"));
354 for (final Jws2Instance sv:alternates.get(service.serviceType))
357 hitm.add(itm=new JMenuItem(sv.getHost()));
358 itm.setForeground(Color.blue);
359 itm.addActionListener(new ActionListener()
363 public void actionPerformed(ActionEvent arg0)
365 new Thread(new Runnable() {
367 setPreferredServiceFor(alignFrame, sv.serviceType, sv.action, sv);
368 changeSupport.firePropertyChange("services", new Vector(), services);
375 /*hitm.addActionListener(new ActionListener()
379 public void actionPerformed(ActionEvent arg0)
381 new Thread(new Runnable() {
385 new SetPreferredServer(alignFrame, service.serviceType, service.action);
394 * add services using the Java 2.5/2.6/2.7 system which optionally creates submenus to index by host and service program type
396 private void addEnumeratedServices(final JMenu jws2al, final AlignFrame alignFrame, List<Jws2Instance> enumerableServices)
398 boolean byhost = Cache.getDefault("WSMENU_BYHOST", false), bytype = Cache
399 .getDefault("WSMENU_BYTYPE", false);
401 * eventually, JWS2 services will appear under the same align/etc submenus.
402 * for moment we keep them separate.
405 MsaWSClient msacl = new MsaWSClient();
406 List<String> hostLabels = new ArrayList<String>();
407 Hashtable<String, String> lasthostFor = new Hashtable<String, String>();
408 Hashtable<String, ArrayList<Jws2Instance>> hosts = new Hashtable<String, ArrayList<Jws2Instance>>();
409 ArrayList<String> hostlist=new ArrayList<String>();
410 for (Jws2Instance service : enumerableServices)
412 ArrayList<Jws2Instance> hostservices = hosts.get(service.getHost());
413 if (hostservices == null)
415 hosts.put(service.getHost(),
416 hostservices = new ArrayList<Jws2Instance>());
417 hostlist.add(service.getHost());
419 hostservices.add(service);
421 // now add hosts in order of the given array
422 for (String host : hostlist)
424 Jws2Instance orderedsvcs[] = hosts.get(host).toArray(
425 new Jws2Instance[1]);
426 String sortbytype[] = new String[orderedsvcs.length];
427 for (int i = 0; i < sortbytype.length; i++)
429 sortbytype[i] = orderedsvcs[i].serviceType;
431 jalview.util.QuickSort.sort(sortbytype, orderedsvcs);
432 for (final Jws2Instance service : orderedsvcs)
434 atpoint = JvSwingUtils.findOrCreateMenu(jws2al,service.action);
435 String type = service.serviceType;
438 atpoint = JvSwingUtils.findOrCreateMenu(atpoint, host);
439 if (atpoint.getToolTipText() == null)
441 atpoint.setToolTipText("Services at " + host);
446 atpoint = JvSwingUtils.findOrCreateMenu(atpoint, type);
447 if (atpoint.getToolTipText() == null)
449 atpoint.setToolTipText(service.getActionText());
453 && !hostLabels.contains(host + service.serviceType
454 + service.getActionText()))
455 // !hostLabels.contains(host + (bytype ?
456 // service.serviceType+service.getActionText() : "")))
458 // add a marker indicating where this service is hosted
459 // relies on services from the same host being listed in a
463 if (hostLabels.contains(host)) {
464 atpoint.addSeparator();
466 hostLabels.add(host);
468 if (lasthostFor.get(service.action) == null || !lasthostFor.get(service.action).equals(host))
470 atpoint.add(hitm = new JMenuItem(host));
471 hitm.setForeground(Color.blue);
472 hitm.addActionListener(new ActionListener()
476 public void actionPerformed(ActionEvent e)
478 Desktop.showUrl(service.getHost());
481 hitm.setToolTipText(JvSwingUtils
482 .wrapTooltip("Opens the JABAWS server's homepage in web browser"));
483 lasthostFor.put(service.action,host);
485 hostLabels.add(host + service.serviceType
486 + service.getActionText());
489 service.attachWSMenuEntry(atpoint, alignFrame);
493 public static void main(String[] args)
497 testUrls = new Vector<String>();
498 for (String url:args)
503 Thread runner = getDiscoverer().startDiscoverer(
504 new PropertyChangeListener()
507 public void propertyChange(PropertyChangeEvent evt)
509 if (getDiscoverer().services != null)
511 System.out.println("Changesupport: There are now "
512 + getDiscoverer().services.size() + " services");
514 for (Jws2Instance instance:getDiscoverer().services)
516 System.out.println("Service "+i+++" "+instance.getClass()+"@"+instance.getHost()+": "+instance.getActionText());
522 while (runner.isAlive())
527 } catch (InterruptedException e)
534 } catch (InterruptedException x) {}
537 private static Jws2Discoverer discoverer;
539 public static Jws2Discoverer getDiscoverer()
541 if (discoverer == null)
543 discoverer = new Jws2Discoverer();
548 public boolean hasServices()
550 // TODO Auto-generated method stub
551 return !running && services != null && services.size() > 0;
554 public boolean isRunning()
560 * the jalview .properties entry for JWS2 URLS
562 final static String JWS2HOSTURLS = "JWS2HOSTURLS";
564 public static void setServiceUrls(Vector<String> urls)
568 StringBuffer urlbuffer = new StringBuffer();
570 for (String url : urls)
572 urlbuffer.append(sep);
573 urlbuffer.append(url);
576 Cache.setProperty(JWS2HOSTURLS, urlbuffer.toString());
580 Cache.removeProperty(JWS2HOSTURLS);
584 private static Vector<String> testUrls=null;
585 public static Vector<String> getServiceUrls()
589 // return test urls, if there are any, instead of touching cache
592 String surls = Cache.getDefault(JWS2HOSTURLS,
593 "http://www.compbio.dundee.ac.uk/jabaws");
594 Vector<String> urls = new Vector<String>();
597 StringTokenizer st = new StringTokenizer(surls, ",");
598 while (st.hasMoreElements())
603 java.net.URL u = new java.net.URL(url = st.nextToken());
604 if (!urls.contains(url))
610 jalview.bin.Cache.log.info("Ignoring duplicate url in "
611 + JWS2HOSTURLS + " list");
613 } catch (Exception ex)
615 jalview.bin.Cache.log
616 .warn("Problem whilst trying to make a URL from '"
617 + ((url != null) ? url : "<null>") + "'");
618 jalview.bin.Cache.log
619 .warn("This was probably due to a malformed comma separated list"
622 + " entry of $(HOME)/.jalview_properties)");
623 jalview.bin.Cache.log.debug("Exception was ", ex);
626 } catch (Exception ex)
628 jalview.bin.Cache.log.warn(
629 "Error parsing comma separated list of urls in "
630 + JWS2HOSTURLS + " preference.", ex);
632 if (urls.size() >= 0)
639 public Vector<Jws2Instance> getServices()
641 return (services == null) ? new Vector<Jws2Instance>()
642 : new Vector<Jws2Instance>(services);
646 * test the given URL with the JabaWS test code
651 public static boolean testServiceUrl(URL foo)
655 compbio.ws.client.WSTester.main(new String[]
656 { "-h=" + foo.toString() });
657 } catch (Exception e)
661 } catch (OutOfMemoryError e)
675 * Start a fresh discovery thread and notify the given object when we're
676 * finished. Any known existing threads will be killed before this one is
679 * @param changeSupport2
682 public Thread startDiscoverer(PropertyChangeListener changeSupport2)
688 addPropertyChangeListener(changeSupport2);
689 Thread thr = new Thread(this);
694 Vector<String> invalidServiceUrls = null, urlsWithoutServices = null, validServiceUrls=null;
697 * @return the invalidServiceUrls
699 public Vector<String> getInvalidServiceUrls()
701 return invalidServiceUrls;
705 * @return the urlsWithoutServices
707 public Vector<String> getUrlsWithoutServices()
709 return urlsWithoutServices;
713 * add an 'empty' JABA server to the list. Only servers not already in the
714 * 'bad URL' list will be added to this list.
718 public synchronized void addUrlwithnoservices(String jwsservers)
720 if (urlsWithoutServices == null)
722 urlsWithoutServices = new Vector<String>();
725 if ((invalidServiceUrls == null || !invalidServiceUrls
726 .contains(jwsservers))
727 && !urlsWithoutServices.contains(jwsservers))
729 urlsWithoutServices.add(jwsservers);
734 * add a bad URL to the list
738 public synchronized void addInvalidServiceUrl(String jwsservers)
740 if (invalidServiceUrls == null)
742 invalidServiceUrls = new Vector<String>();
744 if (!invalidServiceUrls.contains(jwsservers))
746 invalidServiceUrls.add(jwsservers);
752 * @return a human readable report of any problems with the service URLs used
755 public String getErrorMessages()
757 if (!isRunning() && !isAborted())
759 StringBuffer ermsg = new StringBuffer();
760 boolean list = false;
761 if (getInvalidServiceUrls() != null
762 && getInvalidServiceUrls().size() > 0)
764 ermsg.append("URLs that could not be contacted: \n");
765 for (String svcurl : getInvalidServiceUrls())
772 ermsg.append(svcurl);
774 ermsg.append("\n\n");
777 if (getUrlsWithoutServices() != null
778 && getUrlsWithoutServices().size() > 0)
780 ermsg.append("URLs without any JABA Services : \n");
781 for (String svcurl : getUrlsWithoutServices())
788 ermsg.append(svcurl);
792 if (ermsg.length() > 1)
794 return ermsg.toString();
801 public int getServerStatusFor(String url)
803 if (validServiceUrls!=null && validServiceUrls.contains(url))
807 if (urlsWithoutServices!=null && urlsWithoutServices.contains(url))
809 if (invalidServiceUrls!=null && invalidServiceUrls.contains(url))
817 * pick the user's preferred service based on a set of URLs (jaba server locations) and service URIs (specifying version and service interface class)
820 * @return null or best match for given uri/ls.
822 public Jws2Instance getPreferredServiceFor(String[] serviceURLs)
824 HashSet<String> urls=new HashSet<String>();
825 urls.addAll(Arrays.asList(serviceURLs));
826 Jws2Instance match=null;
827 for (Jws2Instance svc:services)
829 if (urls.contains(svc.getServiceTypeURI()))
832 // for moment we always pick service from server ordered first in user's preferences
835 if (urls.contains(svc.getUri()))
837 // stop and return - we've matched type URI and URI for service endpoint
845 Map<String, Map<String, String>> preferredServiceMap = new HashMap<String, Map<String, String>>();
849 * get current preferred service of the given type, or global default
850 * @param af null or a specific alignFrame
851 * @param serviceType Jws2Instance.serviceType for service
852 * @return null if no service of this type is available, the preferred service for the serviceType and af if specified and if defined.
854 public Jws2Instance getPreferredServiceFor(AlignFrame af,
857 String serviceurl = null;
858 synchronized (preferredServiceMap)
860 String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
861 Map<String, String> prefmap = preferredServiceMap.get(afid);
862 if (afid.length() > 0 && prefmap == null)
864 // recover global setting, if any
865 prefmap = preferredServiceMap.get("");
869 serviceurl = prefmap.get(serviceType);
873 Jws2Instance response = null;
874 for (Jws2Instance svc : services)
876 if (svc.serviceType.equals(serviceType))
878 if (serviceurl == null || serviceurl.equals(svc.getHost()))
888 public void setPreferredServiceFor(AlignFrame af, String serviceType,
889 String serviceAction, Jws2Instance selectedServer)
891 String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
892 if (preferredServiceMap == null)
894 preferredServiceMap = new HashMap<String, Map<String, String>>();
896 Map<String, String> prefmap = preferredServiceMap.get(afid);
899 prefmap = new HashMap<String, String>();
900 preferredServiceMap.put(afid, prefmap);
902 prefmap.put(serviceType, selectedServer.getHost());
903 prefmap.put(serviceAction, selectedServer.getHost());
906 public void setPreferredServiceFor(String serviceType,
907 String serviceAction, Jws2Instance selectedServer)
909 setPreferredServiceFor(null, serviceType, serviceAction, selectedServer);