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