early bail out for long urls, and threaded off group url construction: JAL-402
[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     SequenceI[] seqs = alignFrame.getViewport().getSelectionAsNewSequence();
370     SequenceGroup sg = alignFrame.getViewport().getSelectionGroup();
371     if (sg == null)
372     {
373       // consider visible regions here/
374     }
375     enfinServiceMenu.removeAll();
376     JMenu entries = buildGroupURLMenu(seqs, sg);
377     if (entries != null)
378     {
379       for (int i = 0, iSize = entries.getMenuComponentCount(); i < iSize; i++)
380       {
381         // transfer - menu component is removed from entries automatically
382         enfinServiceMenu.add(entries.getMenuComponent(0));
383       }
384       // entries.removeAll();
385       enfinServiceMenu.setEnabled(true);
386     }
387     else
388     {
389       enfinServiceMenu.setEnabled(false);
390     }
391   }
392
393   /**
394    * construct a dynamic enfin services menu given a sequence selection
395    * 
396    * @param seqs
397    * @param sg
398    * @param groupLinks
399    * @return
400    */
401   private JMenu buildGroupURLMenu(SequenceI[] seqs, SequenceGroup sg)
402   {
403
404     // TODO: usability: thread off the generation of group url content so root
405     // menu appears asap
406     // sequence only URLs
407     // ID/regex match URLs
408     JMenu groupLinksMenu = new JMenu("Group Link");
409     String[][] idandseqs = GroupUrlLink.formStrings(seqs);
410     Hashtable commonDbrefs = new Hashtable();
411     for (int sq = 0; sq < seqs.length; sq++)
412     {
413
414       int start, end;
415       if (sg != null)
416       {
417         start = seqs[sq].findPosition(sg.getStartRes());
418         end = seqs[sq].findPosition(sg.getEndRes());
419       }
420       else
421       {
422         // get total width of alignment.
423         start = seqs[sq].getStart();
424         end = seqs[sq].findPosition(seqs[sq].getLength());
425       }
426       // we skip sequences which do not have any non-gaps in the region of
427       // interest
428       if (start > end)
429       {
430         continue;
431       }
432       // just collect ids from dataset sequence
433       // TODO: check if IDs collected from selecton group intersects with the
434       // current selection, too
435       SequenceI sqi = seqs[sq];
436       while (sqi.getDatasetSequence() != null)
437       {
438         sqi = sqi.getDatasetSequence();
439       }
440       DBRefEntry[] dbr = sqi.getDBRef();
441       if (dbr != null && dbr.length > 0)
442       {
443         for (int d = 0; d < dbr.length; d++)
444         {
445           String src = dbr[d].getSource(); // jalview.util.DBRefUtils.getCanonicalName(dbr[d].getSource()).toUpperCase();
446           Object[] sarray = (Object[]) commonDbrefs.get(src);
447           if (sarray == null)
448           {
449             sarray = new Object[2];
450             sarray[0] = new int[]
451             { 0 };
452             sarray[1] = new String[seqs.length];
453
454             commonDbrefs.put(src, sarray);
455           }
456
457           if (((String[]) sarray[1])[sq] == null)
458           {
459             if (!dbr[d].hasMap()
460                     || (dbr[d].getMap().locateMappedRange(start, end) != null))
461             {
462               ((String[]) sarray[1])[sq] = dbr[d].getAccessionId();
463               ((int[]) sarray[0])[0]++;
464             }
465           }
466         }
467       }
468     }
469     // now create group links for all distinct ID/sequence sets.
470     Hashtable<String, JMenu[]> gurlMenus = new Hashtable<String, JMenu[]>();
471     /**
472      * last number of sequences where URL generation failed
473      */
474     int[] nsqtype = new int[] { 0,0,0,0,0,0,0,0,0,0};
475     for (int i = 0; i < groupURLLinks.size(); i++)
476     {
477       String link = groupURLLinks.elementAt(i).toString();
478       String descr = groupURLdescr.elementAt(i).toString();
479
480       // boolean specialCase =
481       // additionalPar.elementAt(i).toString().equals(BACKGROUND);
482       GroupUrlLink urlLink = null;
483       try
484       {
485         urlLink = new GroupUrlLink(link);
486       } catch (Exception foo)
487       {
488         jalview.bin.Cache.log.error("Exception for GroupURLLink '" + link
489                 + "'", foo);
490         continue;
491       }
492       ;
493       if (!urlLink.isValid())
494       {
495         jalview.bin.Cache.log.error(urlLink.getInvalidMessage());
496         continue;
497       }
498       final String label = urlLink.getLabel();
499       // create/recover the sub menus that might be populated for this link.
500       JMenu[] wflinkMenus = gurlMenus.get(label);
501       if (wflinkMenus == null)
502       {
503         // three types of url that might be
504         // created.
505         wflinkMenus = new JMenu[]
506         { null, new JMenu("IDS"), new JMenu("Sequences"),
507             new JMenu("IDS and Sequences") };
508         gurlMenus.put(label, wflinkMenus);
509       }
510
511       boolean usingNames = false;
512       // Now see which parts of the group apply for this URL
513       String ltarget;
514       String[] seqstr, ids; // input to makeUrl
515       for (int t = 0; t < allowedDb.length; t++)
516       {
517         ltarget = allowedDb[t]; // jalview.util.DBRefUtils.getCanonicalName(urlLink.getTarget());
518         Object[] idset = (Object[]) commonDbrefs.get(ltarget.toUpperCase());
519         if (idset != null)
520         {
521           int numinput = ((int[]) idset[0])[0];
522           String[] allids = ((String[]) idset[1]);
523           seqstr = new String[numinput];
524           ids = new String[numinput];
525           if (nsqtype[urlLink.getGroupURLType()]>0 && numinput>=nsqtype[urlLink.getGroupURLType()])
526           {
527             continue;
528           }
529           for (int sq = 0, idcount = 0; sq < seqs.length; sq++)
530           {
531             if (allids[sq] != null)
532             {
533               ids[idcount] = allids[sq];
534               seqstr[idcount++] = idandseqs[1][sq];
535             }
536           }
537           try {createAndAddLinks(wflinkMenus, false, urlLink, ltarget, null,
538                   descr, ids, seqstr);
539           } catch (UrlStringTooLongException ex)
540           {
541             nsqtype[urlLink.getGroupURLType()] = numinput;
542           }
543         }
544       }
545       // also do names only.
546       seqstr = idandseqs[1];
547       ids = idandseqs[0];
548       if (nsqtype[urlLink.getGroupURLType()]>0 && idandseqs[0].length>=nsqtype[urlLink.getGroupURLType()])
549       {
550         continue;
551       }
552       
553       try {createAndAddLinks(wflinkMenus, true, urlLink, "Any", null, descr,
554               ids, seqstr);
555       }catch (UrlStringTooLongException ex)
556       {
557         nsqtype[urlLink.getGroupURLType()] = idandseqs[0].length;
558       }
559     }
560     boolean anyadded = false; // indicates if there are any group links to give
561     // to user
562     for (Map.Entry<String, JMenu[]> menues : gurlMenus.entrySet())
563     {
564       JMenu grouplinkset = new JMenu(menues.getKey());
565       JMenu[] wflinkMenus = menues.getValue();
566       for (int m = 0; m < wflinkMenus.length; m++)
567       {
568         if (wflinkMenus[m] != null
569                 && wflinkMenus[m].getMenuComponentCount() > 0)
570         {
571           anyadded = true;
572           grouplinkset.add(wflinkMenus[m]);
573         }
574       }
575       groupLinksMenu.add(grouplinkset);
576     }
577     if (anyadded)
578     {
579       return groupLinksMenu;
580     }
581     return null;
582   }
583
584   private boolean createAndAddLinks(JMenu[] linkMenus, boolean usingNames,
585           GroupUrlLink urlLink, String label, String ltarget, String descr,
586           String[] ids, String[] seqstr) throws UrlStringTooLongException
587   {
588     Object[] urlset= urlLink.makeUrlStubs(ids, seqstr, "FromJalview"
589             + System.currentTimeMillis(), false);
590
591     if (urlset != null)
592     {
593       int type = urlLink.getGroupURLType() & 3;
594       // System.out.println(urlLink.getGroupURLType()
595       // +" "+((String[])urlset[3])[0]);
596       // first two bits ofurlLink type bitfield are sequenceids and sequences
597       // TODO: FUTURE: ensure the groupURL menu structure can be generalised
598       addshowLink(
599               linkMenus[type],
600               label
601                       + " "
602                       + (ltarget == null ? (((type & 1) == 1 ? "ID"
603                               : "Sequence") + (urlLink
604                               .getNumberInvolved(urlset) > 1 ? "s" : ""))
605                               : (usingNames ? (((type & 1) == 1) ? "(Names)"
606                                       : "")
607                                       : ("(" + ltarget + ")"))), descr,
608               usingNames ? null : label, urlLink, urlset);
609       return true;
610     }
611     return false;
612   }
613   // / end of stuff copied from popupmenu
614   public void attachWSMenuEntry(final JMenu wsmenu,
615           final AlignFrame alignFrame)
616   {
617     final JMenu enfinServiceMenu = new JMenu("Envision 2");
618     wsmenu.add(enfinServiceMenu);
619     enfinServiceMenu.setEnabled(false);
620     wsmenu.addMenuListener(new MenuListener()
621     {
622       // this listener remembers when the menu was first selected, and
623       // doesn't rebuild the session list until it has been cleared and
624       // reselected again.
625       boolean refresh = true;
626
627       public void menuCanceled(MenuEvent e)
628       {
629         refresh = true;
630       }
631
632       public void menuDeselected(MenuEvent e)
633       {
634         refresh = true;
635       }
636
637       public void menuSelected(MenuEvent e)
638       {
639         if (refresh)
640         {
641           new Thread(new Runnable() {
642             public void run() {
643           try
644           {
645             buildGroupLinkMenu(enfinServiceMenu, alignFrame);
646           } catch (OutOfMemoryError ex)
647           {
648             Cache.log.error(
649                     "Out of memory when calculating the Envision2 links.",
650                     ex);
651             enfinServiceMenu.setEnabled(false);
652           }
653             }}).start();
654           refresh = false;
655         }
656       }
657     });
658
659   }
660
661 }