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