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