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