JAL-1503 update version in GPL header
[jalview.git] / src / jalview / ws / jws2 / Jws2Discoverer.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.1)
3  * Copyright (C) 2014 The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
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.
10  *  
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.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  * The Jalview Authors are detailed in the 'AUTHORS' file.
18  */
19 package jalview.ws.jws2;
20
21 import jalview.bin.Cache;
22 import jalview.gui.AlignFrame;
23 import jalview.gui.Desktop;
24 import jalview.gui.JvSwingUtils;
25 import jalview.ws.WSMenuEntryProviderI;
26 import jalview.ws.jws2.jabaws2.Jws2Instance;
27 import jalview.ws.params.ParamDatastoreI;
28
29 import java.awt.Color;
30 import java.awt.event.ActionEvent;
31 import java.awt.event.ActionListener;
32 import java.beans.PropertyChangeEvent;
33 import java.beans.PropertyChangeListener;
34 import java.net.URL;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.Hashtable;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Set;
43 import java.util.StringTokenizer;
44 import java.util.Vector;
45
46 import javax.swing.JMenu;
47 import javax.swing.JMenuItem;
48
49 import compbio.ws.client.Services;
50
51 /**
52  * discoverer for jws2 services. Follows the lightweight service discoverer
53  * pattern (archetyped by EnfinEnvision2OneWay)
54  * 
55  * @author JimP
56  * 
57  */
58 public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
59 {
60   private java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(
61           this);
62
63   /**
64    * change listeners are notified of "services" property changes
65    * 
66    * @param listener
67    *          to be added that consumes new services Hashtable object.
68    */
69   public void addPropertyChangeListener(
70           java.beans.PropertyChangeListener listener)
71   {
72     changeSupport.addPropertyChangeListener(listener);
73   }
74
75   /**
76    * 
77    * 
78    * @param listener
79    *          to be removed
80    */
81   public void removePropertyChangeListener(
82           java.beans.PropertyChangeListener listener)
83   {
84     changeSupport.removePropertyChangeListener(listener);
85   }
86
87   boolean running = false, aborted = false;
88
89   /**
90    * @return the aborted
91    */
92   public boolean isAborted()
93   {
94     return aborted;
95   }
96
97   /**
98    * @param aborted
99    *          the aborted to set
100    */
101   public void setAborted(boolean aborted)
102   {
103     this.aborted = aborted;
104   }
105
106   Thread oldthread = null;
107
108   public void run()
109   {
110
111     if (running && oldthread != null && oldthread.isAlive())
112     {
113       if (!aborted)
114       {
115         return;
116       }
117       while (running)
118       {
119         try
120         {
121           Cache.log
122                   .debug("Waiting around for old discovery thread to finish.");
123           // wait around until old discoverer dies
124           Thread.sleep(100);
125         } catch (Exception e)
126         {
127         }
128       }
129       Cache.log.debug("Old discovery thread has finished.");
130     }
131     running = true;
132     
133     // first set up exclusion list if needed
134     final Set<String> ignoredServices = new HashSet<String>();
135     for (String ignored:jalview.bin.Cache.getDefault("IGNORED_JABAWS_SERVICETYPES", Services.JpredWS.toString()).split("\\|"))
136     {
137       ignoredServices.add(ignored);
138     }
139
140     
141     changeSupport.firePropertyChange("services", services, new Vector());
142     oldthread = Thread.currentThread();
143     try
144     {
145       Class foo = getClass().getClassLoader().loadClass(
146               "compbio.ws.client.Jws2Client");
147     } catch (ClassNotFoundException e)
148     {
149       System.err
150               .println("Not enabling JABA Webservices : client jar is not available."
151                       + "\nPlease check that your webstart JNLP file is up to date!");
152       running = false;
153       return;
154     }
155     // reinitialise records of good and bad service URLs
156     if (services != null)
157     {
158       services.removeAllElements();
159     }
160     if (urlsWithoutServices != null)
161     {
162       urlsWithoutServices.removeAllElements();
163     }
164     if (invalidServiceUrls != null)
165     {
166       invalidServiceUrls.removeAllElements();
167     }
168     if (validServiceUrls != null)
169     {
170       validServiceUrls.removeAllElements();
171     }
172     ArrayList<String> svctypes = new ArrayList<String>();
173
174     List<JabaWsServerQuery> qrys = new ArrayList<JabaWsServerQuery>();
175     for (final String jwsservers : getServiceUrls())
176     {
177       JabaWsServerQuery squery = new JabaWsServerQuery(this, jwsservers);
178       if (svctypes.size() == 0)
179       {
180         // TODO: remove this ugly hack to get Canonical JABA service ordering
181         // for all possible services
182         for (Services sv : squery.JABAWS2SERVERS)
183         {
184           if (!ignoredServices.contains(sv.toString()))
185           {
186             svctypes.add(sv.toString());
187           }
188         }
189
190       }
191       qrys.add(squery);
192       new Thread(squery).start();
193     }
194     boolean finished = true;
195     do
196     {
197       finished = true;
198       try
199       {
200         Thread.sleep(100);
201       } catch (Exception e)
202       {
203       }
204       ;
205       for (JabaWsServerQuery squery : qrys)
206       {
207         if (squery.isRunning()){
208           finished=false;
209         }
210       }
211       if (aborted)
212       {
213         Cache.log.debug("Aborting " + qrys.size()
214                 + " JABAWS discovery threads.");
215         for (JabaWsServerQuery squery : qrys)
216         {
217           squery.setQuit(true);
218         }
219       }
220     } while (!aborted && !finished);
221     if (!aborted)
222     {
223       // resort services according to order found in jabaws service list
224       // also ensure servics for each host are ordered in same way.
225
226       if (services != null && services.size() > 0)
227       {
228         Jws2Instance[] svcs = new Jws2Instance[services.size()];
229         int[] spos = new int[services.size()];
230         int ipos = 0;
231         Vector svcUrls = getServiceUrls();
232         for (Jws2Instance svc : services)
233         {
234           svcs[ipos] = svc;
235           spos[ipos++] = 1000 * svcUrls.indexOf(svc.getHost()) + 1
236                   + svctypes.indexOf(svc.serviceType);
237         }
238         jalview.util.QuickSort.sort(spos, svcs);
239         services = new Vector<Jws2Instance>();
240         for (Jws2Instance svc : svcs)
241         {
242           if (!ignoredServices.contains(svc.serviceType))
243           {
244             services.add(svc);
245           }
246         }
247       }
248     }
249     oldthread = null;
250     running = false;
251     changeSupport.firePropertyChange("services", new Vector(), services);
252   }
253
254   /**
255    * record this service endpoint so we can use it
256    * 
257    * @param jwsservers
258    * @param srv
259    * @param service2
260    */
261   synchronized void addService(String jwsservers, Jws2Instance service)
262   {
263     if (services == null)
264     {
265       services = new Vector<Jws2Instance>();
266     }
267     System.out.println("Discovered service: " + jwsservers + " "
268             + service.toString());
269     // Jws2Instance service = new Jws2Instance(jwsservers, srv.toString(),
270     // service2);
271
272     services.add(service);
273     // retrieve the presets and parameter set and cache now
274     ParamDatastoreI pds = service.getParamStore();
275     if (pds != null)
276     {
277       pds.getPresets();
278     }
279     service.hasParameters();
280     if (validServiceUrls == null)
281     {
282       validServiceUrls = new Vector();
283     }
284     validServiceUrls.add(jwsservers);
285   }
286
287   /**
288    * holds list of services.
289    */
290   protected Vector<Jws2Instance> services;
291
292   /**
293    * attach all available web services to the appropriate submenu in the given
294    * JMenu
295    */
296   public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
297   {
298     // dynamically regenerate service list.
299     populateWSMenuEntry(wsmenu, alignFrame, null);
300   }
301
302   private boolean isRecalculable(String action)
303   {
304     return (action != null && action.equalsIgnoreCase("conservation"));
305   }
306
307   private void populateWSMenuEntry(JMenu jws2al,
308           final AlignFrame alignFrame, String typeFilter)
309   {
310     if (running || services == null || services.size() == 0)
311     {
312       return;
313     }
314     boolean byhost = Cache.getDefault("WSMENU_BYHOST", false), bytype = Cache
315             .getDefault("WSMENU_BYTYPE", false);
316     /**
317      * eventually, JWS2 services will appear under the same align/etc submenus.
318      * for moment we keep them separate.
319      */
320     JMenu atpoint;
321     List<Jws2Instance> enumerableServices = new ArrayList<Jws2Instance>();
322     // jws2al.removeAll();
323     Map<String, Jws2Instance> preferredHosts = new HashMap<String, Jws2Instance>();
324     Map<String, List<Jws2Instance>> alternates = new HashMap<String, List<Jws2Instance>>();
325     for (Jws2Instance service : services.toArray(new Jws2Instance[0]))
326     {
327       if (!isRecalculable(service.action))
328       {
329         // add 'one shot' services to be displayed using the classic menu
330         // structure
331         enumerableServices.add(service);
332       }
333       else
334       {
335         if (!preferredHosts.containsKey(service.serviceType))
336         {
337           Jws2Instance preferredInstance = getPreferredServiceFor(
338                   alignFrame, service.serviceType);
339           if (preferredInstance != null)
340           {
341             preferredHosts.put(service.serviceType, preferredInstance);
342           }
343           else
344           {
345             preferredHosts.put(service.serviceType, service);
346           }
347         }
348         List<Jws2Instance> ph = alternates.get(service.serviceType);
349         if (preferredHosts.get(service.serviceType) != service)
350         {
351           if (ph == null)
352           {
353             ph = new ArrayList<Jws2Instance>();
354           }
355           ph.add(service);
356           alternates.put(service.serviceType, ph);
357         }
358       }
359
360     }
361
362     // create GUI element for classic services
363     addEnumeratedServices(jws2al, alignFrame, enumerableServices);
364     // and the instantaneous services
365     for (final Jws2Instance service : preferredHosts.values())
366     {
367       atpoint = JvSwingUtils.findOrCreateMenu(jws2al, service.action);
368       JMenuItem hitm;
369       if (atpoint.getItemCount() > 1)
370       {
371         // previous service of this type already present
372         atpoint.addSeparator();
373       }
374       atpoint.add(hitm = new JMenuItem(service.getHost()));
375       hitm.setForeground(Color.blue);
376       hitm.addActionListener(new ActionListener()
377       {
378
379         @Override
380         public void actionPerformed(ActionEvent e)
381         {
382           Desktop.showUrl(service.getHost());
383         }
384       });
385       hitm.setToolTipText(JvSwingUtils
386               .wrapTooltip("Opens the JABAWS server's homepage in web browser"));
387
388       service.attachWSMenuEntry(atpoint, alignFrame);
389       if (alternates.containsKey(service.serviceType))
390       {
391         atpoint.add(hitm = new JMenu("Switch server"));
392         hitm.setToolTipText(JvSwingUtils
393                 .wrapTooltip("Choose a server for running this service"));
394         for (final Jws2Instance sv : alternates.get(service.serviceType))
395         {
396           JMenuItem itm;
397           hitm.add(itm = new JMenuItem(sv.getHost()));
398           itm.setForeground(Color.blue);
399           itm.addActionListener(new ActionListener()
400           {
401
402             @Override
403             public void actionPerformed(ActionEvent arg0)
404             {
405               new Thread(new Runnable()
406               {
407                 public void run()
408                 {
409                   setPreferredServiceFor(alignFrame, sv.serviceType,
410                           sv.action, sv);
411                   changeSupport.firePropertyChange("services",
412                           new Vector(), services);
413                 };
414               }).start();
415
416             }
417           });
418         }
419         /*
420          * hitm.addActionListener(new ActionListener() {
421          * 
422          * @Override public void actionPerformed(ActionEvent arg0) { new
423          * Thread(new Runnable() {
424          * 
425          * @Override public void run() { new SetPreferredServer(alignFrame,
426          * service.serviceType, service.action); } }).start(); } });
427          */
428       }
429     }
430   }
431
432   /**
433    * add services using the Java 2.5/2.6/2.7 system which optionally creates
434    * submenus to index by host and service program type
435    */
436   private void addEnumeratedServices(final JMenu jws2al,
437           final AlignFrame alignFrame, List<Jws2Instance> enumerableServices)
438   {
439     boolean byhost = Cache.getDefault("WSMENU_BYHOST", false), bytype = Cache
440             .getDefault("WSMENU_BYTYPE", false);
441     /**
442      * eventually, JWS2 services will appear under the same align/etc submenus.
443      * for moment we keep them separate.
444      */
445     JMenu atpoint;
446     MsaWSClient msacl = new MsaWSClient();
447     List<String> hostLabels = new ArrayList<String>();
448     Hashtable<String, String> lasthostFor = new Hashtable<String, String>();
449     Hashtable<String, ArrayList<Jws2Instance>> hosts = new Hashtable<String, ArrayList<Jws2Instance>>();
450     ArrayList<String> hostlist = new ArrayList<String>();
451     for (Jws2Instance service : enumerableServices)
452     {
453       ArrayList<Jws2Instance> hostservices = hosts.get(service.getHost());
454       if (hostservices == null)
455       {
456         hosts.put(service.getHost(),
457                 hostservices = new ArrayList<Jws2Instance>());
458         hostlist.add(service.getHost());
459       }
460       hostservices.add(service);
461     }
462     // now add hosts in order of the given array
463     for (String host : hostlist)
464     {
465       Jws2Instance orderedsvcs[] = hosts.get(host).toArray(
466               new Jws2Instance[1]);
467       String sortbytype[] = new String[orderedsvcs.length];
468       for (int i = 0; i < sortbytype.length; i++)
469       {
470         sortbytype[i] = orderedsvcs[i].serviceType;
471       }
472       jalview.util.QuickSort.sort(sortbytype, orderedsvcs);
473       for (final Jws2Instance service : orderedsvcs)
474       {
475         atpoint = JvSwingUtils.findOrCreateMenu(jws2al, service.action);
476         String type = service.serviceType;
477         if (byhost)
478         {
479           atpoint = JvSwingUtils.findOrCreateMenu(atpoint, host);
480           if (atpoint.getToolTipText() == null)
481           {
482             atpoint.setToolTipText("Services at " + host);
483           }
484         }
485         if (bytype)
486         {
487           atpoint = JvSwingUtils.findOrCreateMenu(atpoint, type);
488           if (atpoint.getToolTipText() == null)
489           {
490             atpoint.setToolTipText(service.getActionText());
491           }
492         }
493         if (!byhost
494                 && !hostLabels.contains(host + service.serviceType
495                         + service.getActionText()))
496         // !hostLabels.contains(host + (bytype ?
497         // service.serviceType+service.getActionText() : "")))
498         {
499           // add a marker indicating where this service is hosted
500           // relies on services from the same host being listed in a
501           // contiguous
502           // group
503           JMenuItem hitm;
504           if (hostLabels.contains(host))
505           {
506             atpoint.addSeparator();
507           }
508           else
509           {
510             hostLabels.add(host);
511           }
512           if (lasthostFor.get(service.action) == null
513                   || !lasthostFor.get(service.action).equals(host))
514           {
515             atpoint.add(hitm = new JMenuItem(host));
516             hitm.setForeground(Color.blue);
517             hitm.addActionListener(new ActionListener()
518             {
519
520               @Override
521               public void actionPerformed(ActionEvent e)
522               {
523                 Desktop.showUrl(service.getHost());
524               }
525             });
526             hitm.setToolTipText(JvSwingUtils
527                     .wrapTooltip("Opens the JABAWS server's homepage in web browser"));
528             lasthostFor.put(service.action, host);
529           }
530           hostLabels.add(host + service.serviceType
531                   + service.getActionText());
532         }
533
534         service.attachWSMenuEntry(atpoint, alignFrame);
535       }
536     }
537   }
538
539   public static void main(String[] args)
540   {
541     if (args.length > 0)
542     {
543       testUrls = new Vector<String>();
544       for (String url : args)
545       {
546         testUrls.add(url);
547       }
548       ;
549     }
550     Thread runner = getDiscoverer().startDiscoverer(
551             new PropertyChangeListener()
552             {
553
554               public void propertyChange(PropertyChangeEvent evt)
555               {
556                 if (getDiscoverer().services != null)
557                 {
558                   System.out.println("Changesupport: There are now "
559                           + getDiscoverer().services.size() + " services");
560                   int i = 1;
561                   for (Jws2Instance instance : getDiscoverer().services)
562                   {
563                     System.out.println("Service " + i++ + " "
564                             + instance.getClass() + "@"
565                             + instance.getHost() + ": "
566                             + instance.getActionText());
567                   }
568
569                 }
570               }
571             });
572     while (runner.isAlive())
573     {
574       try
575       {
576         Thread.sleep(50);
577       } catch (InterruptedException e)
578       {
579       }
580       ;
581     }
582     try
583     {
584       Thread.sleep(50);
585     } catch (InterruptedException x)
586     {
587     }
588   }
589
590   private static Jws2Discoverer discoverer;
591
592   public static Jws2Discoverer getDiscoverer()
593   {
594     if (discoverer == null)
595     {
596       discoverer = new Jws2Discoverer();
597     }
598     return discoverer;
599   }
600
601   public boolean hasServices()
602   {
603     // TODO Auto-generated method stub
604     return !running && services != null && services.size() > 0;
605   }
606
607   public boolean isRunning()
608   {
609     return running;
610   }
611
612   /**
613    * the jalview .properties entry for JWS2 URLS
614    */
615   final static String JWS2HOSTURLS = "JWS2HOSTURLS";
616
617   public static void setServiceUrls(Vector<String> urls)
618   {
619     if (urls != null)
620     {
621       StringBuffer urlbuffer = new StringBuffer();
622       String sep = "";
623       for (String url : urls)
624       {
625         urlbuffer.append(sep);
626         urlbuffer.append(url);
627         sep = ",";
628       }
629       Cache.setProperty(JWS2HOSTURLS, urlbuffer.toString());
630     }
631     else
632     {
633       Cache.removeProperty(JWS2HOSTURLS);
634     }
635   }
636
637   private static Vector<String> testUrls = null;
638
639   public static Vector<String> getServiceUrls()
640   {
641     if (testUrls != null)
642     {
643       // return test urls, if there are any, instead of touching cache
644       return testUrls;
645     }
646     String surls = Cache.getDefault(JWS2HOSTURLS,
647             "http://www.compbio.dundee.ac.uk/jabaws");
648     Vector<String> urls = new Vector<String>();
649     try
650     {
651       StringTokenizer st = new StringTokenizer(surls, ",");
652       while (st.hasMoreElements())
653       {
654         String url = null;
655         try
656         {
657           java.net.URL u = new java.net.URL(url = st.nextToken());
658           if (!urls.contains(url))
659           {
660             urls.add(url);
661           }
662           else
663           {
664             jalview.bin.Cache.log.info("Ignoring duplicate url in "
665                     + JWS2HOSTURLS + " list");
666           }
667         } catch (Exception ex)
668         {
669           jalview.bin.Cache.log
670                   .warn("Problem whilst trying to make a URL from '"
671                           + ((url != null) ? url : "<null>") + "'");
672           jalview.bin.Cache.log
673                   .warn("This was probably due to a malformed comma separated list"
674                           + " in the "
675                           + JWS2HOSTURLS
676                           + " entry of $(HOME)/.jalview_properties)");
677           jalview.bin.Cache.log.debug("Exception was ", ex);
678         }
679       }
680     } catch (Exception ex)
681     {
682       jalview.bin.Cache.log.warn(
683               "Error parsing comma separated list of urls in "
684                       + JWS2HOSTURLS + " preference.", ex);
685     }
686     if (urls.size() >= 0)
687     {
688       return urls;
689     }
690     return null;
691   }
692
693   public Vector<Jws2Instance> getServices()
694   {
695     return (services == null) ? new Vector<Jws2Instance>()
696             : new Vector<Jws2Instance>(services);
697   }
698
699   /**
700    * test the given URL with the JabaWS test code
701    * 
702    * @param foo
703    * @return
704    */
705   public static boolean testServiceUrl(URL foo)
706   {
707     try
708     {
709       compbio.ws.client.WSTester.main(new String[]
710       { "-h=" + foo.toString() });
711     } catch (Exception e)
712     {
713       e.printStackTrace();
714       return false;
715     } catch (OutOfMemoryError e)
716     {
717       e.printStackTrace();
718       return false;
719     } catch (Error e)
720     {
721       e.printStackTrace();
722       return false;
723     }
724
725     return true;
726   }
727
728   /**
729    * Start a fresh discovery thread and notify the given object when we're
730    * finished. Any known existing threads will be killed before this one is
731    * started.
732    * 
733    * @param changeSupport2
734    * @return new thread
735    */
736   public Thread startDiscoverer(PropertyChangeListener changeSupport2)
737   {
738     if (isRunning())
739     {
740       setAborted(true);
741     }
742     addPropertyChangeListener(changeSupport2);
743     Thread thr = new Thread(this);
744     thr.start();
745     return thr;
746   }
747
748   Vector<String> invalidServiceUrls = null, urlsWithoutServices = null,
749           validServiceUrls = null;
750
751   /**
752    * @return the invalidServiceUrls
753    */
754   public Vector<String> getInvalidServiceUrls()
755   {
756     return invalidServiceUrls;
757   }
758
759   /**
760    * @return the urlsWithoutServices
761    */
762   public Vector<String> getUrlsWithoutServices()
763   {
764     return urlsWithoutServices;
765   }
766
767   /**
768    * add an 'empty' JABA server to the list. Only servers not already in the
769    * 'bad URL' list will be added to this list.
770    * 
771    * @param jwsservers
772    */
773   public synchronized void addUrlwithnoservices(String jwsservers)
774   {
775     if (urlsWithoutServices == null)
776     {
777       urlsWithoutServices = new Vector<String>();
778     }
779
780     if ((invalidServiceUrls == null || !invalidServiceUrls
781             .contains(jwsservers))
782             && !urlsWithoutServices.contains(jwsservers))
783     {
784       urlsWithoutServices.add(jwsservers);
785     }
786   }
787
788   /**
789    * add a bad URL to the list
790    * 
791    * @param jwsservers
792    */
793   public synchronized void addInvalidServiceUrl(String jwsservers)
794   {
795     if (invalidServiceUrls == null)
796     {
797       invalidServiceUrls = new Vector<String>();
798     }
799     if (!invalidServiceUrls.contains(jwsservers))
800     {
801       invalidServiceUrls.add(jwsservers);
802     }
803   }
804
805   /**
806    * 
807    * @return a human readable report of any problems with the service URLs used
808    *         for discovery
809    */
810   public String getErrorMessages()
811   {
812     if (!isRunning() && !isAborted())
813     {
814       StringBuffer ermsg = new StringBuffer();
815       boolean list = false;
816       if (getInvalidServiceUrls() != null
817               && getInvalidServiceUrls().size() > 0)
818       {
819         ermsg.append("URLs that could not be contacted: \n");
820         for (String svcurl : getInvalidServiceUrls())
821         {
822           if (list)
823           {
824             ermsg.append(", ");
825           }
826           list = true;
827           ermsg.append(svcurl);
828         }
829         ermsg.append("\n\n");
830       }
831       list = false;
832       if (getUrlsWithoutServices() != null
833               && getUrlsWithoutServices().size() > 0)
834       {
835         ermsg.append("URLs without any JABA Services : \n");
836         for (String svcurl : getUrlsWithoutServices())
837         {
838           if (list)
839           {
840             ermsg.append(", ");
841           }
842           list = true;
843           ermsg.append(svcurl);
844         }
845         ermsg.append("\n");
846       }
847       if (ermsg.length() > 1)
848       {
849         return ermsg.toString();
850       }
851
852     }
853     return null;
854   }
855
856   public int getServerStatusFor(String url)
857   {
858     if (validServiceUrls != null && validServiceUrls.contains(url))
859     {
860       return 1;
861     }
862     if (urlsWithoutServices != null && urlsWithoutServices.contains(url))
863       return 0;
864     if (invalidServiceUrls != null && invalidServiceUrls.contains(url))
865     {
866       return -1;
867     }
868     return -2;
869   }
870
871   /**
872    * pick the user's preferred service based on a set of URLs (jaba server
873    * locations) and service URIs (specifying version and service interface
874    * class)
875    * 
876    * @param serviceURL
877    * @return null or best match for given uri/ls.
878    */
879   public Jws2Instance getPreferredServiceFor(String[] serviceURLs)
880   {
881     HashSet<String> urls = new HashSet<String>();
882     urls.addAll(Arrays.asList(serviceURLs));
883     Jws2Instance match = null;
884     if (services != null)
885     {
886       for (Jws2Instance svc : services)
887       {
888         if (urls.contains(svc.getServiceTypeURI()))
889         {
890           if (match == null)
891           {
892             // for moment we always pick service from server ordered first in
893             // user's preferences
894             match = svc;
895           }
896           if (urls.contains(svc.getUri()))
897           {
898             // stop and return - we've matched type URI and URI for service
899             // endpoint
900             return svc;
901           }
902         }
903       }
904     }
905     return match;
906   }
907
908   Map<String, Map<String, String>> preferredServiceMap = new HashMap<String, Map<String, String>>();;
909
910   /**
911    * get current preferred service of the given type, or global default
912    * 
913    * @param af
914    *          null or a specific alignFrame
915    * @param serviceType
916    *          Jws2Instance.serviceType for service
917    * @return null if no service of this type is available, the preferred service
918    *         for the serviceType and af if specified and if defined.
919    */
920   public Jws2Instance getPreferredServiceFor(AlignFrame af,
921           String serviceType)
922   {
923     String serviceurl = null;
924     synchronized (preferredServiceMap)
925     {
926       String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
927       Map<String, String> prefmap = preferredServiceMap.get(afid);
928       if (afid.length() > 0 && prefmap == null)
929       {
930         // recover global setting, if any
931         prefmap = preferredServiceMap.get("");
932       }
933       if (prefmap != null)
934       {
935         serviceurl = prefmap.get(serviceType);
936       }
937
938     }
939     Jws2Instance response = null;
940     for (Jws2Instance svc : services)
941     {
942       if (svc.serviceType.equals(serviceType))
943       {
944         if (serviceurl == null || serviceurl.equals(svc.getHost()))
945         {
946           response = svc;
947           break;
948         }
949       }
950     }
951     return response;
952   }
953
954   public void setPreferredServiceFor(AlignFrame af, String serviceType,
955           String serviceAction, Jws2Instance selectedServer)
956   {
957     String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
958     if (preferredServiceMap == null)
959     {
960       preferredServiceMap = new HashMap<String, Map<String, String>>();
961     }
962     Map<String, String> prefmap = preferredServiceMap.get(afid);
963     if (prefmap == null)
964     {
965       prefmap = new HashMap<String, String>();
966       preferredServiceMap.put(afid, prefmap);
967     }
968     prefmap.put(serviceType, selectedServer.getHost());
969     prefmap.put(serviceAction, selectedServer.getHost());
970   }
971
972   public void setPreferredServiceFor(String serviceType,
973           String serviceAction, Jws2Instance selectedServer)
974   {
975     setPreferredServiceFor(null, serviceType, serviceAction, selectedServer);
976   }
977 }