apply jalview code style
[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,
288           final Object[] urlstub)
289   {
290     Component[] jmi = linkMenu.getMenuComponents();
291     for (int i = 0; i < jmi.length; i++)
292     {
293       if (jmi[i] instanceof JMenuItem
294               && ((JMenuItem) jmi[i]).getText().equalsIgnoreCase(label))
295       {
296         // don't add this - its a repeat of an existing URL.
297         return;
298       }
299     }
300     boolean seqsorids = (urlgenerator.getGroupURLType() & urlgenerator.SEQUENCEIDS) == 0;
301     int i = urlgenerator.getNumberInvolved(urlstub);
302     JMenuItem item = new JMenuItem(label);
303     //
304     if (dbname == null || dbname.trim().length() == 0)
305     {
306       dbname = "";
307     }
308     item.setToolTipText("<html>"
309             + JvSwingUtils.wrapTooltip("Submit " + i + " " + dbname + " "
310                     + (seqsorids ? "sequence" : "sequence id")
311                     + (i > 1 ? "s" : "")
312
313                     + " to<br/>" + descr) + "</html>");
314     item.addActionListener(new java.awt.event.ActionListener()
315     {
316       public void actionPerformed(ActionEvent e)
317       {
318         new Thread(new Runnable()
319         {
320
321           public void run()
322           {
323             try
324             {
325               showLink(urlgenerator.constructFrom(urlstub));
326             } catch (UrlStringTooLongException ex)
327             {
328               Cache.log.warn("Not showing link: URL is too long!", ex);
329             }
330           }
331
332         }).start();
333       }
334     });
335
336     linkMenu.add(item);
337   }
338
339   /**
340    * open the given link in a new browser window
341    * 
342    * @param url
343    */
344   public void showLink(String url)
345   {
346     try
347     {
348       jalview.util.BrowserLauncher.openURL(url);
349     } catch (Exception ex)
350     {
351       JOptionPane
352               .showInternalMessageDialog(
353                       Desktop.desktop,
354                       "Unixers: Couldn't find default web browser."
355                               + "\nAdd the full path to your browser in Preferences.",
356                       "Web browser not found", JOptionPane.WARNING_MESSAGE);
357
358       ex.printStackTrace();
359     }
360   }
361
362   /**
363    * called by a web service menu instance when it is opened.
364    * 
365    * @param enfinServiceMenu
366    * @param alignFrame
367    */
368   private void buildGroupLinkMenu(JMenu enfinServiceMenu,
369           AlignFrame alignFrame)
370   {
371     if (running || !started)
372     {
373       return;
374     }
375     SequenceI[] seqs = alignFrame.getViewport().getSelectionAsNewSequence();
376     SequenceGroup sg = alignFrame.getViewport().getSelectionGroup();
377     if (sg == null)
378     {
379       // consider visible regions here/
380     }
381     enfinServiceMenu.removeAll();
382     JMenu entries = buildGroupURLMenu(seqs, sg);
383     if (entries != null)
384     {
385       for (int i = 0, iSize = entries.getMenuComponentCount(); i < iSize; i++)
386       {
387         // transfer - menu component is removed from entries automatically
388         enfinServiceMenu.add(entries.getMenuComponent(0));
389       }
390       // entries.removeAll();
391       enfinServiceMenu.setEnabled(true);
392     }
393     else
394     {
395       enfinServiceMenu.setEnabled(false);
396     }
397   }
398
399   /**
400    * construct a dynamic enfin services menu given a sequence selection
401    * 
402    * @param seqs
403    * @param sg
404    * @param groupLinks
405    * @return
406    */
407   private JMenu buildGroupURLMenu(SequenceI[] seqs, SequenceGroup sg)
408   {
409     if (groupURLdescr == null || groupURLLinks == null)
410       return null;
411     // TODO: usability: thread off the generation of group url content so root
412     // menu appears asap
413     // sequence only URLs
414     // ID/regex match URLs
415     JMenu groupLinksMenu = new JMenu("Group Link");
416     String[][] idandseqs = GroupUrlLink.formStrings(seqs);
417     Hashtable commonDbrefs = new Hashtable();
418     for (int sq = 0; sq < seqs.length; sq++)
419     {
420
421       int start, end;
422       if (sg != null)
423       {
424         start = seqs[sq].findPosition(sg.getStartRes());
425         end = seqs[sq].findPosition(sg.getEndRes());
426       }
427       else
428       {
429         // get total width of alignment.
430         start = seqs[sq].getStart();
431         end = seqs[sq].findPosition(seqs[sq].getLength());
432       }
433       // we skip sequences which do not have any non-gaps in the region of
434       // interest
435       if (start > end)
436       {
437         continue;
438       }
439       // just collect ids from dataset sequence
440       // TODO: check if IDs collected from selecton group intersects with the
441       // current selection, too
442       SequenceI sqi = seqs[sq];
443       while (sqi.getDatasetSequence() != null)
444       {
445         sqi = sqi.getDatasetSequence();
446       }
447       DBRefEntry[] dbr = sqi.getDBRef();
448       if (dbr != null && dbr.length > 0)
449       {
450         for (int d = 0; d < dbr.length; d++)
451         {
452           String src = dbr[d].getSource(); // jalview.util.DBRefUtils.getCanonicalName(dbr[d].getSource()).toUpperCase();
453           Object[] sarray = (Object[]) commonDbrefs.get(src);
454           if (sarray == null)
455           {
456             sarray = new Object[2];
457             sarray[0] = new int[]
458             { 0 };
459             sarray[1] = new String[seqs.length];
460
461             commonDbrefs.put(src, sarray);
462           }
463
464           if (((String[]) sarray[1])[sq] == null)
465           {
466             if (!dbr[d].hasMap()
467                     || (dbr[d].getMap().locateMappedRange(start, end) != null))
468             {
469               ((String[]) sarray[1])[sq] = dbr[d].getAccessionId();
470               ((int[]) sarray[0])[0]++;
471             }
472           }
473         }
474       }
475     }
476     // now create group links for all distinct ID/sequence sets.
477     Hashtable<String, JMenu[]> gurlMenus = new Hashtable<String, JMenu[]>();
478     /**
479      * last number of sequences where URL generation failed
480      */
481     int[] nsqtype = new int[]
482     { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
483     for (int i = 0; i < groupURLLinks.size(); i++)
484     {
485       String link = (String) groupURLLinks.elementAt(i);
486       String descr = (String) groupURLdescr.elementAt(i);
487
488       // boolean specialCase =
489       // additionalPar.elementAt(i).toString().equals(BACKGROUND);
490       GroupUrlLink urlLink = null;
491       try
492       {
493         urlLink = new GroupUrlLink(link);
494       } catch (Exception foo)
495       {
496         jalview.bin.Cache.log.error("Exception for GroupURLLink '" + link
497                 + "'", foo);
498         continue;
499       }
500       ;
501       if (!urlLink.isValid())
502       {
503         jalview.bin.Cache.log.error(urlLink.getInvalidMessage());
504         continue;
505       }
506       final String label = urlLink.getLabel();
507       // create/recover the sub menus that might be populated for this link.
508       JMenu[] wflinkMenus = gurlMenus.get(label);
509       if (wflinkMenus == null)
510       {
511         // three types of url that might be
512         // created.
513         wflinkMenus = new JMenu[]
514         { null, new JMenu("IDS"), new JMenu("Sequences"),
515             new JMenu("IDS and Sequences") };
516         gurlMenus.put(label, wflinkMenus);
517       }
518
519       boolean usingNames = false;
520       // Now see which parts of the group apply for this URL
521       String ltarget;
522       String[] seqstr, ids; // input to makeUrl
523       for (int t = 0; t < allowedDb.length; t++)
524       {
525         ltarget = allowedDb[t]; // jalview.util.DBRefUtils.getCanonicalName(urlLink.getTarget());
526         Object[] idset = (Object[]) commonDbrefs.get(ltarget.toUpperCase());
527         if (idset != null)
528         {
529           int numinput = ((int[]) idset[0])[0];
530           String[] allids = ((String[]) idset[1]);
531           seqstr = new String[numinput];
532           ids = new String[numinput];
533           if (nsqtype[urlLink.getGroupURLType()] > 0
534                   && numinput >= nsqtype[urlLink.getGroupURLType()])
535           {
536             continue;
537           }
538           for (int sq = 0, idcount = 0; sq < seqs.length; sq++)
539           {
540             if (allids[sq] != null)
541             {
542               ids[idcount] = allids[sq];
543               seqstr[idcount++] = idandseqs[1][sq];
544             }
545           }
546           try
547           {
548             createAndAddLinks(wflinkMenus, false, urlLink, ltarget, null,
549                     descr, ids, seqstr);
550           } catch (UrlStringTooLongException ex)
551           {
552             nsqtype[urlLink.getGroupURLType()] = numinput;
553           }
554         }
555       }
556       // also do names only.
557       seqstr = idandseqs[1];
558       ids = idandseqs[0];
559       if (nsqtype[urlLink.getGroupURLType()] > 0
560               && idandseqs[0].length >= nsqtype[urlLink.getGroupURLType()])
561       {
562         continue;
563       }
564
565       try
566       {
567         createAndAddLinks(wflinkMenus, true, urlLink, "Any", null, descr,
568                 ids, seqstr);
569       } catch (UrlStringTooLongException ex)
570       {
571         nsqtype[urlLink.getGroupURLType()] = idandseqs[0].length;
572       }
573     }
574     boolean anyadded = false; // indicates if there are any group links to give
575     // to user
576     for (Map.Entry<String, JMenu[]> menues : gurlMenus.entrySet())
577     {
578       JMenu grouplinkset = new JMenu(menues.getKey());
579       JMenu[] wflinkMenus = menues.getValue();
580       for (int m = 0; m < wflinkMenus.length; m++)
581       {
582         if (wflinkMenus[m] != null
583                 && wflinkMenus[m].getMenuComponentCount() > 0)
584         {
585           anyadded = true;
586           grouplinkset.add(wflinkMenus[m]);
587         }
588       }
589       groupLinksMenu.add(grouplinkset);
590     }
591     if (anyadded)
592     {
593       return groupLinksMenu;
594     }
595     return null;
596   }
597
598   private boolean createAndAddLinks(JMenu[] linkMenus, boolean usingNames,
599           GroupUrlLink urlLink, String label, String ltarget, String descr,
600           String[] ids, String[] seqstr) throws UrlStringTooLongException
601   {
602     Object[] urlset = urlLink.makeUrlStubs(ids, seqstr, "FromJalview"
603             + System.currentTimeMillis(), false);
604
605     if (urlset != null)
606     {
607       int type = urlLink.getGroupURLType() & 3;
608       // System.out.println(urlLink.getGroupURLType()
609       // +" "+((String[])urlset[3])[0]);
610       // first two bits ofurlLink type bitfield are sequenceids and sequences
611       // TODO: FUTURE: ensure the groupURL menu structure can be generalised
612       addshowLink(
613               linkMenus[type],
614               label
615                       + " "
616                       + (ltarget == null ? (((type & 1) == 1 ? "ID"
617                               : "Sequence") + (urlLink
618                               .getNumberInvolved(urlset) > 1 ? "s" : ""))
619                               : (usingNames ? (((type & 1) == 1) ? "(Names)"
620                                       : "")
621                                       : ("(" + ltarget + ")"))), descr,
622               usingNames ? null : label, urlLink, urlset);
623       return true;
624     }
625     return false;
626   }
627
628   // / end of stuff copied from popupmenu
629   public void attachWSMenuEntry(final JMenu wsmenu,
630           final AlignFrame alignFrame)
631   {
632     final JMenu enfinServiceMenu = new JMenu("Envision 2");
633     wsmenu.add(enfinServiceMenu);
634     enfinServiceMenu.setEnabled(false);
635     wsmenu.addMenuListener(new MenuListener()
636     {
637       // this listener remembers when the menu was first selected, and
638       // doesn't rebuild the session list until it has been cleared and
639       // reselected again.
640       boolean refresh = true;
641
642       public void menuCanceled(MenuEvent e)
643       {
644         refresh = true;
645       }
646
647       public void menuDeselected(MenuEvent e)
648       {
649         refresh = true;
650       }
651
652       public void menuSelected(MenuEvent e)
653       {
654         if (refresh && !isRunning())
655         {
656           new Thread(new Runnable()
657           {
658             public void run()
659             {
660               try
661               {
662                 buildGroupLinkMenu(enfinServiceMenu, alignFrame);
663               } catch (OutOfMemoryError ex)
664               {
665                 Cache.log
666                         .error("Out of memory when calculating the Envision2 links.",
667                                 ex);
668                 enfinServiceMenu.setEnabled(false);
669               }
670             }
671           }).start();
672           refresh = false;
673         }
674       }
675     });
676
677   }
678
679 }