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