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