stop race condition when submenu being populated before workflows are discovered
[jalview.git] / src / jalview / ws / EnfinEnvision2OneWay.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, 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;
19
20 import jalview.bin.Cache;
21 import jalview.datamodel.DBRefEntry;
22 import jalview.datamodel.SequenceGroup;
23 import jalview.datamodel.SequenceI;
24 import jalview.gui.AlignFrame;
25 import jalview.gui.Desktop;
26 import jalview.gui.JvSwingUtils;
27 import jalview.util.GroupUrlLink;
28 import jalview.util.GroupUrlLink.UrlStringTooLongException;
29
30 import java.awt.Component;
31 import java.awt.Cursor;
32 import java.awt.event.ActionEvent;
33 import java.awt.event.ActionListener;
34 import java.awt.event.ItemEvent;
35 import java.awt.event.ItemListener;
36 import java.io.BufferedReader;
37 import java.io.InputStreamReader;
38 import java.net.URL;
39 import java.util.Hashtable;
40 import java.util.Map;
41 import java.util.Vector;
42
43 import javax.swing.JMenu;
44 import javax.swing.JMenuItem;
45 import javax.swing.JOptionPane;
46 import javax.swing.event.MenuEvent;
47 import javax.swing.event.MenuListener;
48 import javax.xml.parsers.SAXParser;
49 import javax.xml.parsers.SAXParserFactory;
50
51 import org.xml.sax.Attributes;
52 import org.xml.sax.SAXException;
53 import org.xml.sax.helpers.DefaultHandler;
54
55 /**
56  * Lightweight runnable to discover dynamic 'one way' group URL services
57  * 
58  * @author JimP
59  * 
60  */
61 public class EnfinEnvision2OneWay extends DefaultHandler implements
62         Runnable, WSMenuEntryProviderI
63 {
64   private static EnfinEnvision2OneWay groupURLLinksGatherer = null;
65
66   public static EnfinEnvision2OneWay getInstance()
67   {
68     if (groupURLLinksGatherer == null)
69     {
70       groupURLLinksGatherer = new EnfinEnvision2OneWay();
71     }
72     return groupURLLinksGatherer;
73   }
74
75   private void waitForCompletion()
76   {
77     if (groupURLLinksGatherer.isRunning())
78     {
79       // wait around and show a visual delay indicator
80       Cursor oldCursor = Desktop.instance.getCursor();
81       Desktop.instance.setCursor(new Cursor(Cursor.WAIT_CURSOR));
82       while (groupURLLinksGatherer.isRunning())
83       {
84         try
85         {
86           Thread.sleep(100);
87         } catch (InterruptedException e)
88         {
89         }
90         ;
91       }
92       Desktop.instance.setCursor(oldCursor);
93     }
94   }
95
96   public Vector getEnvisionServiceGroupURLS()
97   {
98     waitForCompletion();
99     return groupURLLinks;
100   }
101
102   /**
103    * indicate if
104    */
105   private static String BACKGROUND = "BACKGROUNDPARAM";
106
107   /**
108    * contains null strings or one of the above constants - indicate if this URL
109    * is a special case.
110    */
111   private Vector additionalPar = new Vector();
112
113   /**
114    * the enfin service URL
115    */
116   private String enfinService = null;
117
118   private String description = null;
119
120   private String wfname;
121
122   /*
123    * (non-Javadoc)
124    * 
125    * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String,
126    * java.lang.String, java.lang.String)
127    */
128   public void endElement(String uri, String localName, String qName)
129           throws SAXException
130   {
131
132     // System.err.println("End element: : '"+uri+" "+localName+" "+qName);
133     if (qName.equalsIgnoreCase("workflow") && description != null
134             && description.length() > 0)
135     {
136       // groupURLLinks.addElement("UNIPROT|EnVision2|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?tool=Jalview&workflow=Default&datasetName=JalviewIDs$DATASETID$&input=$SEQUENCEIDS$&inputType=0|,");
137       // groupURLLinks.addElement("Seqs|EnVision2|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?tool=Jalview&workflow=Default&datasetName=JalviewSeqs$DATASETID$&input=$SEQUENCES=/([A-Za-z]+)+/=$&inputType=1|,");
138       System.err.println("Adding entry for " + wfname + " " + description);
139       if (wfname.toLowerCase().indexOf("funcnet") == -1)
140       {
141         groupURLdescr.addElement(description);
142         groupURLdescr.addElement(description);
143         groupURLLinks
144                 .addElement(wfname
145                         + "|"
146                         + "http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?tool=Jalview&workflow="
147                         + wfname
148                         + "&datasetName=JalviewSeqs$DATASETID$&input=$SEQUENCEIDS$&inputType=0|,"); // #"+description+"#");
149         groupURLLinks
150                 .addElement(wfname
151                         + "|"
152                         + "http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?tool=Jalview&workflow="
153                         + wfname
154                         + "&datasetName=JalviewSeqs$DATASETID$&input=$SEQUENCES=/([A-Za-z]+)+/=$&inputType=1|,"); // #"+description+"#");
155       }
156     }
157   }
158
159   /*
160    * (non-Javadoc)
161    * 
162    * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
163    */
164   public void characters(char[] ch, int start, int length)
165           throws SAXException
166   {
167     if (description != null)
168     {
169       for (int i = start; i < start + length; i++)
170       {
171         description += ch[i];
172       }
173     }
174   }
175
176   /*
177    * (non-Javadoc)
178    * 
179    * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String,
180    * java.lang.String, java.lang.String, org.xml.sax.Attributes)
181    */
182   public void startElement(String uri, String localName, String qName,
183           Attributes attributes) throws SAXException
184   {
185     if (qName.equalsIgnoreCase("workflow"))
186     {
187       description = null;
188       wfname = attributes.getValue("name");
189     }
190     if (qName.equalsIgnoreCase("description"))
191     {
192       description = "";
193     }
194
195     // System.err.println("Start element: : '"+uri+" "+localName+" "+qName+" attributes"+attributes);
196     // super.startElement(uri,localName,qname,attributes);
197   }
198
199   private boolean started = false;
200
201   private boolean running = false;
202
203   private Vector groupURLLinks = null;
204
205   private Vector groupURLdescr = null;
206
207   private static String[] allowedDb = new String[]
208   { "UNIPROT", "EMBL", "PDB" };
209
210   public EnfinEnvision2OneWay()
211   {
212     groupURLLinks = new Vector();
213     groupURLdescr = new Vector();
214
215     enfinService = Cache.getDefault("ENVISION2_WORKFLOWSERVICE",
216             "http://www.ebi.ac.uk/enfin-srv/envision2/pages/workflows.xml");
217     new Thread(this).start();
218   }
219
220   public void run()
221   {
222     started = true;
223     running = true;
224     try
225     {
226       SAXParserFactory spf = SAXParserFactory.newInstance();
227       SAXParser sp = spf.newSAXParser();
228       sp.parse(new URL(enfinService).openStream(), this);
229     } catch (Exception e)
230     {
231       Cache.log.warn("Exception when discovering One Way services: ", e);
232     } catch (Error e)
233     {
234       Cache.log.warn("Error when discovering One Way services: ", e);
235     }
236     running = false;
237     Cache.log.debug("Finished running.");
238   }
239
240   /**
241    * have we finished running yet ?
242    * 
243    * @return false if we have been run.
244    */
245   public boolean isRunning()
246   {
247
248     // TODO Auto-generated method stub
249     return !started || running;
250   }
251
252   public static void main(String[] args)
253   {
254     Cache.initLogger();
255     EnfinEnvision2OneWay ow = new EnfinEnvision2OneWay();
256     while (ow.isRunning())
257     {
258       try
259       {
260         Thread.sleep(50);
261       } catch (Exception e)
262       {
263       }
264       ;
265
266     }
267     for (int i = 0; i < ow.groupURLLinks.size(); i++)
268     {
269       System.err.println("Description" + ow.groupURLdescr.elementAt(i)
270               + "Service URL: " + ow.groupURLLinks.elementAt(i));
271     }
272   }
273
274   // / Copied from jalview.gui.PopupMenu
275   /**
276    * add a late bound URL service item to the given menu
277    * 
278    * @param linkMenu
279    * @param label
280    *          - menu label string
281    * @param urlgenerator
282    *          GroupURLLink used to generate URL
283    * @param urlstub
284    *          Object array returned from the makeUrlStubs function.
285    */
286   private void addshowLink(JMenu linkMenu, String label, String descr,
287           String dbname, final GroupUrlLink urlgenerator, final Object[] urlstub)
288   {
289     Component[] jmi = linkMenu.getMenuComponents();
290     for (int i = 0; i < jmi.length; i++)
291     {
292       if (jmi[i] instanceof JMenuItem
293               && ((JMenuItem) jmi[i]).getText().equalsIgnoreCase(label))
294       {
295         // don't add this - its a repeat of an existing URL.
296         return;
297       }
298     }
299     boolean seqsorids = (urlgenerator.getGroupURLType() & urlgenerator.SEQUENCEIDS) == 0;
300     int i = urlgenerator.getNumberInvolved(urlstub);
301     JMenuItem item = new JMenuItem(label);
302     // 
303     if (dbname==null || dbname.trim().length()==0)
304     {
305       dbname = "";
306     }
307     item.setToolTipText("<html>"
308             + JvSwingUtils.wrapTooltip("Submit " + i + " " +
309                     dbname +" "
310                     + (seqsorids ? "sequence" : "sequence id") + (i > 1 ? "s" : "")
311                     
312             + " to<br/>" + descr) + "</html>");
313     item.addActionListener(new java.awt.event.ActionListener()
314     {
315       public void actionPerformed(ActionEvent e)
316       {
317         new Thread(new Runnable()
318         {
319
320           public void run()
321           {
322             try {
323               showLink(urlgenerator.constructFrom(urlstub));
324             } catch (UrlStringTooLongException ex)
325             {
326               Cache.log.warn("Not showing link: URL is too long!", ex);
327             }
328           }
329
330         }).start();
331       }
332     });
333
334     linkMenu.add(item);
335   }
336
337   /**
338    * open the given link in a new browser window
339    * 
340    * @param url
341    */
342   public void showLink(String url)
343   {
344     try
345     {
346       jalview.util.BrowserLauncher.openURL(url);
347     } catch (Exception ex)
348     {
349       JOptionPane
350               .showInternalMessageDialog(
351                       Desktop.desktop,
352                       "Unixers: Couldn't find default web browser."
353                               + "\nAdd the full path to your browser in Preferences.",
354                       "Web browser not found", JOptionPane.WARNING_MESSAGE);
355
356       ex.printStackTrace();
357     }
358   }
359
360   /**
361    * called by a web service menu instance when it is opened.
362    * 
363    * @param enfinServiceMenu
364    * @param alignFrame
365    */
366   private void buildGroupLinkMenu(JMenu enfinServiceMenu,
367           AlignFrame alignFrame)
368   {
369     if (running || !started)
370     {
371       return;
372     }
373     SequenceI[] seqs = alignFrame.getViewport().getSelectionAsNewSequence();
374     SequenceGroup sg = alignFrame.getViewport().getSelectionGroup();
375     if (sg == null)
376     {
377       // consider visible regions here/
378     }
379     enfinServiceMenu.removeAll();
380     JMenu entries = buildGroupURLMenu(seqs, sg);
381     if (entries != null)
382     {
383       for (int i = 0, iSize = entries.getMenuComponentCount(); i < iSize; i++)
384       {
385         // transfer - menu component is removed from entries automatically
386         enfinServiceMenu.add(entries.getMenuComponent(0));
387       }
388       // entries.removeAll();
389       enfinServiceMenu.setEnabled(true);
390     }
391     else
392     {
393       enfinServiceMenu.setEnabled(false);
394     }
395   }
396
397   /**
398    * construct a dynamic enfin services menu given a sequence selection
399    * 
400    * @param seqs
401    * @param sg
402    * @param groupLinks
403    * @return
404    */
405   private JMenu buildGroupURLMenu(SequenceI[] seqs, SequenceGroup sg)
406   {
407
408     // TODO: usability: thread off the generation of group url content so root
409     // menu appears asap
410     // sequence only URLs
411     // ID/regex match URLs
412     JMenu groupLinksMenu = new JMenu("Group Link");
413     String[][] idandseqs = GroupUrlLink.formStrings(seqs);
414     Hashtable commonDbrefs = new Hashtable();
415     for (int sq = 0; sq < seqs.length; sq++)
416     {
417
418       int start, end;
419       if (sg != null)
420       {
421         start = seqs[sq].findPosition(sg.getStartRes());
422         end = seqs[sq].findPosition(sg.getEndRes());
423       }
424       else
425       {
426         // get total width of alignment.
427         start = seqs[sq].getStart();
428         end = seqs[sq].findPosition(seqs[sq].getLength());
429       }
430       // we skip sequences which do not have any non-gaps in the region of
431       // interest
432       if (start > end)
433       {
434         continue;
435       }
436       // just collect ids from dataset sequence
437       // TODO: check if IDs collected from selecton group intersects with the
438       // current selection, too
439       SequenceI sqi = seqs[sq];
440       while (sqi.getDatasetSequence() != null)
441       {
442         sqi = sqi.getDatasetSequence();
443       }
444       DBRefEntry[] dbr = sqi.getDBRef();
445       if (dbr != null && dbr.length > 0)
446       {
447         for (int d = 0; d < dbr.length; d++)
448         {
449           String src = dbr[d].getSource(); // jalview.util.DBRefUtils.getCanonicalName(dbr[d].getSource()).toUpperCase();
450           Object[] sarray = (Object[]) commonDbrefs.get(src);
451           if (sarray == null)
452           {
453             sarray = new Object[2];
454             sarray[0] = new int[]
455             { 0 };
456             sarray[1] = new String[seqs.length];
457
458             commonDbrefs.put(src, sarray);
459           }
460
461           if (((String[]) sarray[1])[sq] == null)
462           {
463             if (!dbr[d].hasMap()
464                     || (dbr[d].getMap().locateMappedRange(start, end) != null))
465             {
466               ((String[]) sarray[1])[sq] = dbr[d].getAccessionId();
467               ((int[]) sarray[0])[0]++;
468             }
469           }
470         }
471       }
472     }
473     // now create group links for all distinct ID/sequence sets.
474     Hashtable<String, JMenu[]> gurlMenus = new Hashtable<String, JMenu[]>();
475     /**
476      * last number of sequences where URL generation failed
477      */
478     int[] nsqtype = new int[] { 0,0,0,0,0,0,0,0,0,0};
479     for (int i = 0; i < groupURLLinks.size(); i++)
480     {
481       String link = groupURLLinks.elementAt(i).toString();
482       String descr = groupURLdescr.elementAt(i).toString();
483
484       // boolean specialCase =
485       // additionalPar.elementAt(i).toString().equals(BACKGROUND);
486       GroupUrlLink urlLink = null;
487       try
488       {
489         urlLink = new GroupUrlLink(link);
490       } catch (Exception foo)
491       {
492         jalview.bin.Cache.log.error("Exception for GroupURLLink '" + link
493                 + "'", foo);
494         continue;
495       }
496       ;
497       if (!urlLink.isValid())
498       {
499         jalview.bin.Cache.log.error(urlLink.getInvalidMessage());
500         continue;
501       }
502       final String label = urlLink.getLabel();
503       // create/recover the sub menus that might be populated for this link.
504       JMenu[] wflinkMenus = gurlMenus.get(label);
505       if (wflinkMenus == null)
506       {
507         // three types of url that might be
508         // created.
509         wflinkMenus = new JMenu[]
510         { null, new JMenu("IDS"), new JMenu("Sequences"),
511             new JMenu("IDS and Sequences") };
512         gurlMenus.put(label, wflinkMenus);
513       }
514
515       boolean usingNames = false;
516       // Now see which parts of the group apply for this URL
517       String ltarget;
518       String[] seqstr, ids; // input to makeUrl
519       for (int t = 0; t < allowedDb.length; t++)
520       {
521         ltarget = allowedDb[t]; // jalview.util.DBRefUtils.getCanonicalName(urlLink.getTarget());
522         Object[] idset = (Object[]) commonDbrefs.get(ltarget.toUpperCase());
523         if (idset != null)
524         {
525           int numinput = ((int[]) idset[0])[0];
526           String[] allids = ((String[]) idset[1]);
527           seqstr = new String[numinput];
528           ids = new String[numinput];
529           if (nsqtype[urlLink.getGroupURLType()]>0 && numinput>=nsqtype[urlLink.getGroupURLType()])
530           {
531             continue;
532           }
533           for (int sq = 0, idcount = 0; sq < seqs.length; sq++)
534           {
535             if (allids[sq] != null)
536             {
537               ids[idcount] = allids[sq];
538               seqstr[idcount++] = idandseqs[1][sq];
539             }
540           }
541           try {createAndAddLinks(wflinkMenus, false, urlLink, ltarget, null,
542                   descr, ids, seqstr);
543           } catch (UrlStringTooLongException ex)
544           {
545             nsqtype[urlLink.getGroupURLType()] = numinput;
546           }
547         }
548       }
549       // also do names only.
550       seqstr = idandseqs[1];
551       ids = idandseqs[0];
552       if (nsqtype[urlLink.getGroupURLType()]>0 && idandseqs[0].length>=nsqtype[urlLink.getGroupURLType()])
553       {
554         continue;
555       }
556       
557       try {createAndAddLinks(wflinkMenus, true, urlLink, "Any", null, descr,
558               ids, seqstr);
559       }catch (UrlStringTooLongException ex)
560       {
561         nsqtype[urlLink.getGroupURLType()] = idandseqs[0].length;
562       }
563     }
564     boolean anyadded = false; // indicates if there are any group links to give
565     // to user
566     for (Map.Entry<String, JMenu[]> menues : gurlMenus.entrySet())
567     {
568       JMenu grouplinkset = new JMenu(menues.getKey());
569       JMenu[] wflinkMenus = menues.getValue();
570       for (int m = 0; m < wflinkMenus.length; m++)
571       {
572         if (wflinkMenus[m] != null
573                 && wflinkMenus[m].getMenuComponentCount() > 0)
574         {
575           anyadded = true;
576           grouplinkset.add(wflinkMenus[m]);
577         }
578       }
579       groupLinksMenu.add(grouplinkset);
580     }
581     if (anyadded)
582     {
583       return groupLinksMenu;
584     }
585     return null;
586   }
587
588   private boolean createAndAddLinks(JMenu[] linkMenus, boolean usingNames,
589           GroupUrlLink urlLink, String label, String ltarget, String descr,
590           String[] ids, String[] seqstr) throws UrlStringTooLongException
591   {
592     Object[] urlset= urlLink.makeUrlStubs(ids, seqstr, "FromJalview"
593             + System.currentTimeMillis(), false);
594
595     if (urlset != null)
596     {
597       int type = urlLink.getGroupURLType() & 3;
598       // System.out.println(urlLink.getGroupURLType()
599       // +" "+((String[])urlset[3])[0]);
600       // first two bits ofurlLink type bitfield are sequenceids and sequences
601       // TODO: FUTURE: ensure the groupURL menu structure can be generalised
602       addshowLink(
603               linkMenus[type],
604               label
605                       + " "
606                       + (ltarget == null ? (((type & 1) == 1 ? "ID"
607                               : "Sequence") + (urlLink
608                               .getNumberInvolved(urlset) > 1 ? "s" : ""))
609                               : (usingNames ? (((type & 1) == 1) ? "(Names)"
610                                       : "")
611                                       : ("(" + ltarget + ")"))), descr,
612               usingNames ? null : label, urlLink, urlset);
613       return true;
614     }
615     return false;
616   }
617   // / end of stuff copied from popupmenu
618   public void attachWSMenuEntry(final JMenu wsmenu,
619           final AlignFrame alignFrame)
620   {
621     final JMenu enfinServiceMenu = new JMenu("Envision 2");
622     wsmenu.add(enfinServiceMenu);
623     enfinServiceMenu.setEnabled(false);
624     wsmenu.addMenuListener(new MenuListener()
625     {
626       // this listener remembers when the menu was first selected, and
627       // doesn't rebuild the session list until it has been cleared and
628       // reselected again.
629       boolean refresh = true;
630
631       public void menuCanceled(MenuEvent e)
632       {
633         refresh = true;
634       }
635
636       public void menuDeselected(MenuEvent e)
637       {
638         refresh = true;
639       }
640
641       public void menuSelected(MenuEvent e)
642       {
643         if (refresh)
644         {
645           new Thread(new Runnable() {
646             public void run() {
647           try
648           {
649             buildGroupLinkMenu(enfinServiceMenu, alignFrame);
650           } catch (OutOfMemoryError ex)
651           {
652             Cache.log.error(
653                     "Out of memory when calculating the Envision2 links.",
654                     ex);
655             enfinServiceMenu.setEnabled(false);
656           }
657             }}).start();
658           refresh = false;
659         }
660       }
661     });
662
663   }
664
665 }