upgrade vamsas client to 0.1 (selections)
[jalview.git] / src / jalview / gui / VamsasApplication.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)
3  * Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.gui;
20
21 import jalview.bin.Cache;
22 import jalview.datamodel.SequenceI;
23 import jalview.io.VamsasAppDatastore;
24 import jalview.structure.StructureSelectionManager;
25 import jalview.structure.VamsasListener;
26
27 import java.beans.PropertyChangeEvent;
28 import java.beans.PropertyChangeListener;
29 import java.io.File;
30 import java.io.IOException;
31 import java.util.Hashtable;
32 import java.util.IdentityHashMap;
33
34 import javax.swing.JInternalFrame;
35 import javax.swing.JOptionPane;
36
37 import uk.ac.vamsas.client.ClientHandle;
38 import uk.ac.vamsas.client.IClient;
39 import uk.ac.vamsas.client.IClientDocument;
40 import uk.ac.vamsas.client.InvalidSessionDocumentException;
41 import uk.ac.vamsas.client.NoDefaultSessionException;
42 import uk.ac.vamsas.client.UserHandle;
43 import uk.ac.vamsas.client.VorbaId;
44 import uk.ac.vamsas.client.picking.IMessageHandler;
45 import uk.ac.vamsas.client.picking.IPickManager;
46 import uk.ac.vamsas.client.picking.Message;
47 import uk.ac.vamsas.client.picking.MouseOverMessage;
48 import uk.ac.vamsas.client.picking.SelectionMessage;
49 import uk.ac.vamsas.objects.core.Entry;
50
51 /**
52  * @author jimp
53  * 
54  */
55 public class VamsasApplication
56 {
57   IClient vclient = null;
58
59   ClientHandle app = null;
60
61   UserHandle user = null;
62
63   Desktop jdesktop = null; // our jalview desktop reference
64
65   // Cache.preferences for vamsas client session arena
66   // preferences for check for default session at startup.
67   // user and organisation stuff.
68   public VamsasApplication(Desktop jdesktop, File sessionPath)
69   {
70     // JBPNote:
71     // we should create a session URI from the sessionPath and pass it to
72     // the clientFactory - but the vamsas api doesn't cope with that yet.
73     this.jdesktop = jdesktop;
74     initClientSession(null, sessionPath);
75   }
76
77   private static uk.ac.vamsas.client.IClientFactory getClientFactory()
78           throws IOException
79   {
80     return new uk.ac.vamsas.client.simpleclient.SimpleClientFactory();
81   }
82
83   /**
84    * Start a new vamsas session
85    * 
86    * @param jdesktop
87    */
88   public VamsasApplication(Desktop jdesktop)
89   {
90     this.jdesktop = jdesktop;
91     initClientSession(null, null);
92   }
93
94   /**
95    * init a connection to the session at the given url
96    * 
97    * @param jdesktop
98    * @param sessionUrl
99    */
100   public VamsasApplication(Desktop jdesktop, String sessionUrl)
101   {
102     this.jdesktop = jdesktop;
103     initClientSession(sessionUrl, null);
104   }
105
106   /**
107    * @throws IOException
108    *                 or other if clientfactory instantiation failed.
109    * @return list of current sessions or null if no session exists.
110    */
111   public static String[] getSessionList() throws Exception
112   {
113     return getClientFactory().getCurrentSessions();
114   }
115
116   /**
117    * initialise, possibly with either a valid session url or a file for a new
118    * session
119    * 
120    * @param sess
121    *                null or a valid session url
122    * @param vamsasDocument
123    *                null or a valid vamsas document file
124    * @return false if no vamsas connection was made
125    */
126   private boolean initClientSession(String sess, File vamsasDocument)
127   {
128     try
129     {
130       // Only need to tell the library what the application is here
131       app = getJalviewHandle();
132       uk.ac.vamsas.client.IClientFactory clientfactory = getClientFactory();
133       if (vamsasDocument != null)
134       {
135         if (sess != null)
136         {
137           throw new Error(
138                   "Implementation Error - cannot import existing vamsas document into an existing session, Yet!");
139         }
140         try
141         {
142           vclient = clientfactory.openAsNewSessionIClient(app,
143                   vamsasDocument);
144         } catch (InvalidSessionDocumentException e)
145         {
146           JOptionPane
147                   .showInternalMessageDialog(
148                           Desktop.desktop,
149
150                           "VAMSAS Document could not be opened as a new session - please choose another",
151                           "VAMSAS Document Import Failed",
152                           JOptionPane.ERROR_MESSAGE);
153
154         }
155       }
156       else
157       {
158         // join existing or create a new session
159         if (sess == null)
160         {
161           vclient = clientfactory.getNewSessionIClient(app);
162         }
163         else
164         {
165           vclient = clientfactory.getIClient(app, sess);
166         }
167       }
168       // set some properties for our VAMSAS interaction
169       setVclientConfig();
170       user = vclient.getUserHandle();
171
172     } catch (Exception e)
173     {
174       jalview.bin.Cache.log
175               .error("Couldn't instantiate vamsas client !", e);
176       return false;
177     }
178     return true;
179   }
180
181   private void setVclientConfig()
182   {
183     if (vclient == null)
184     {
185       return;
186     }
187     try
188     {
189       if (vclient instanceof uk.ac.vamsas.client.simpleclient.SimpleClient)
190       {
191         uk.ac.vamsas.client.simpleclient.SimpleClientConfig cfg = ((uk.ac.vamsas.client.simpleclient.SimpleClient) vclient)
192                 .getSimpleClientConfig();
193         cfg._validatemergedroots = false;
194         cfg._validateupdatedroots = true; // we may write rubbish otherwise.
195       }
196     } catch (Error e)
197     {
198       Cache.log
199               .warn(
200                       "Probable SERIOUS VAMSAS client incompatibility - carrying on regardless",
201                       e);
202     } catch (Exception e)
203     {
204       Cache.log
205               .warn(
206                       "Probable VAMSAS client incompatibility - carrying on regardless",
207                       e);
208     }
209   }
210
211   /**
212    * make the appHandle for Jalview
213    * 
214    * @return
215    */
216   private ClientHandle getJalviewHandle()
217   {
218     return new ClientHandle("jalview.bin.Jalview", jalview.bin.Cache
219             .getProperty("VERSION"));
220   }
221
222   /**
223    * 
224    * @return true if we are registered in a vamsas session
225    */
226   public boolean inSession()
227   {
228     return (vclient != null);
229   }
230
231   /**
232    * called to connect to session inits handlers, does an initial document
233    * update.
234    */
235   public void initial_update()
236   {
237     if (!inSession())
238     {
239       throw new Error(
240               "Impementation error! Vamsas Operations when client not initialised and connected.");
241     }
242     addDocumentUpdateHandler();
243     addStoreDocumentHandler();
244     startSession();
245     Cache.log
246             .debug("Jalview loading the Vamsas Session for the first time.");
247     dealWithDocumentUpdate(false); // we don't push an update out to the
248     // document yet.
249     Cache.log.debug("... finished update for the first time.");
250   }
251
252   /**
253    * Update all windows after a vamsas datamodel change. this could go on the
254    * desktop object!
255    * 
256    */
257   protected void updateJalviewGui()
258   {
259     JInternalFrame[] frames = jdesktop.getAllFrames();
260
261     if (frames == null)
262     {
263       return;
264     }
265
266     try
267     {
268       // REVERSE ORDER
269       for (int i = frames.length - 1; i > -1; i--)
270       {
271         if (frames[i] instanceof AlignFrame)
272         {
273           AlignFrame af = (AlignFrame) frames[i];
274           af.alignPanel.alignmentChanged();
275         }
276       }
277     } catch (Exception e)
278     {
279       Cache.log
280               .warn(
281                       "Exception whilst refreshing jalview windows after a vamsas document update.",
282                       e);
283     }
284   }
285
286   public void push_update()
287   {
288     Thread udthread = new Thread(new Runnable()
289     {
290
291       public void run()
292       {
293         Cache.log.info("Jalview updating to the Vamsas Session.");
294
295         dealWithDocumentUpdate(true);
296         /*
297          * IClientDocument cdoc=null; try { cdoc = vclient.getClientDocument(); }
298          * catch (Exception e) { Cache.log.error("Failed to get client document
299          * for update."); // RAISE A WARNING DIALOG disableGui(false); return; }
300          * updateVamsasDocument(cdoc); updateJalviewGui();
301          * cdoc.setVamsasRoots(cdoc.getVamsasRoots()); // propagate update flags
302          * back vclient.updateDocument(cdoc);
303          */
304         Cache.log.info("Jalview finished updating to the Vamsas Session.");
305         // TODO Auto-generated method stub
306       }
307
308     });
309     udthread.start();
310   }
311
312   public void end_session()
313   {
314     if (!inSession())
315       throw new Error("Jalview not connected to Vamsas session.");
316     Cache.log.info("Jalview disconnecting from the Vamsas Session.");
317     try
318     {
319       if (joinedSession)
320       {
321         vclient.finalizeClient();
322         Cache.log.info("Jalview has left the session.");
323       }
324       else
325       {
326         Cache.log
327                 .warn("JV Client leaving a session that's its not joined yet.");
328       }
329       joinedSession = false;
330       vclient = null;
331       app = null;
332       user = null;
333       jv2vobj = null;
334       vobj2jv = null;
335     } catch (Exception e)
336     {
337       Cache.log.error("Vamsas Session finalization threw exceptions!", e);
338     }
339   }
340
341   public void updateJalview(IClientDocument cdoc)
342   {
343     Cache.log.debug("Jalview updating from sesion document ..");
344     ensureJvVamsas();
345     VamsasAppDatastore vds = new VamsasAppDatastore(cdoc, vobj2jv, jv2vobj,
346             baseProvEntry(), alRedoState);
347     vds.updateToJalview();
348     Cache.log.debug(".. finished updating from sesion document.");
349
350   }
351
352   private void ensureJvVamsas()
353   {
354     if (jv2vobj == null)
355     {
356       jv2vobj = new IdentityHashMap();
357       vobj2jv = new Hashtable();
358       alRedoState = new Hashtable();
359     }
360   }
361
362   /**
363    * jalview object binding to VorbaIds
364    */
365   IdentityHashMap jv2vobj = null;
366
367   Hashtable vobj2jv = null;
368
369   Hashtable alRedoState = null;
370
371   public void updateVamsasDocument(IClientDocument doc)
372   {
373     ensureJvVamsas();
374     VamsasAppDatastore vds = new VamsasAppDatastore(doc, vobj2jv, jv2vobj,
375             baseProvEntry(), alRedoState);
376     // wander through frames
377     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
378
379     if (frames == null)
380     {
381       return;
382     }
383
384     try
385     {
386       // REVERSE ORDER
387       for (int i = frames.length - 1; i > -1; i--)
388       {
389         if (frames[i] instanceof AlignFrame)
390         {
391           AlignFrame af = (AlignFrame) frames[i];
392
393           // update alignment and root from frame.
394           vds.storeVAMSAS(af.getViewport(), af.getTitle());
395         }
396       }
397       // REVERSE ORDER
398       for (int i = frames.length - 1; i > -1; i--)
399       {
400         if (frames[i] instanceof AlignFrame)
401         {
402           AlignFrame af = (AlignFrame) frames[i];
403
404           // add any AlignedCodonFrame mappings on this alignment to any other.
405           vds.storeSequenceMappings(af.getViewport(), af.getTitle());
406         }
407       }
408     } catch (Exception e)
409     {
410       Cache.log.error("Vamsas Document store exception", e);
411     }
412   }
413
414   private Entry baseProvEntry()
415   {
416     uk.ac.vamsas.objects.core.Entry pentry = new uk.ac.vamsas.objects.core.Entry();
417     pentry.setUser(user.getFullName());
418     pentry.setApp(app.getClientUrn());
419     pentry.setDate(new java.util.Date());
420     pentry.setAction("created");
421     return pentry;
422   }
423
424   /**
425    * do a vamsas document update or update jalview from the vamsas document
426    * 
427    * @param fromJalview
428    *                true to update from jalview to the vamsas document
429    */
430   protected void dealWithDocumentUpdate(boolean fromJalview)
431   {
432     // called by update handler for document update.
433     Cache.log.debug("Updating jalview from changed vamsas document.");
434     disableGui(true);
435     try
436     {
437       long time = System.currentTimeMillis();
438       IClientDocument cdoc = vclient.getClientDocument();
439       if (Cache.log.isDebugEnabled())
440       {
441         Cache.log.debug("Time taken to get ClientDocument = "
442                 + (System.currentTimeMillis() - time));
443         time = System.currentTimeMillis();
444       }
445       if (fromJalview)
446       {
447         this.updateVamsasDocument(cdoc);
448         if (Cache.log.isDebugEnabled())
449         {
450           Cache.log
451                   .debug("Time taken to update Vamsas Document from jalview\t= "
452                           + (System.currentTimeMillis() - time));
453           time = System.currentTimeMillis();
454         }
455         cdoc.setVamsasRoots(cdoc.getVamsasRoots());
456         if (Cache.log.isDebugEnabled())
457         {
458           Cache.log.debug("Time taken to set Document Roots\t\t= "
459                   + (System.currentTimeMillis() - time));
460           time = System.currentTimeMillis();
461         }
462       }
463       else
464       {
465         updateJalview(cdoc);
466         if (Cache.log.isDebugEnabled())
467         {
468           Cache.log
469                   .debug("Time taken to update Jalview from vamsas document Roots\t= "
470                           + (System.currentTimeMillis() - time));
471           time = System.currentTimeMillis();
472         }
473
474       }
475       vclient.updateDocument(cdoc);
476       if (Cache.log.isDebugEnabled())
477       {
478         Cache.log.debug("Time taken to update Session Document\t= "
479                 + (System.currentTimeMillis() - time));
480         time = System.currentTimeMillis();
481       }
482       cdoc = null;
483     } catch (Exception ee)
484     {
485       System.err.println("Exception whilst updating :");
486       ee.printStackTrace(System.err);
487     }
488     Cache.log.debug("Finished updating from document change.");
489     disableGui(false);
490   }
491
492   private void addDocumentUpdateHandler()
493   {
494     final VamsasApplication client = this;
495     vclient.addDocumentUpdateHandler(new PropertyChangeListener()
496     {
497       public void propertyChange(PropertyChangeEvent evt)
498       {
499         Cache.log.debug("Dealing with document update event.");
500         client.dealWithDocumentUpdate(false);
501         Cache.log.debug("finished dealing with event.");
502       }
503     });
504     Cache.log.debug("Added Jalview handler for vamsas document updates.");
505   }
506
507   private void addStoreDocumentHandler()
508   {
509     final VamsasApplication client = this;
510     vclient.addVorbaEventHandler(
511             uk.ac.vamsas.client.Events.DOCUMENT_REQUESTTOCLOSE,
512             new PropertyChangeListener()
513             {
514               public void propertyChange(PropertyChangeEvent evt)
515               {
516                 Cache.log
517                         .debug("Asking user if the vamsas session should be stored.");
518                 int reply = JOptionPane
519                         .showInternalConfirmDialog(
520                                 Desktop.desktop,
521                                 "The current VAMSAS session has unsaved data - do you want to save it ?",
522                                 "VAMSAS Session Shutdown",
523                                 JOptionPane.YES_NO_OPTION,
524                                 JOptionPane.QUESTION_MESSAGE);
525
526                 if (reply == JOptionPane.YES_OPTION)
527                 {
528                   Cache.log.debug("Prompting for vamsas store filename.");
529                   Desktop.instance.vamsasSave_actionPerformed(null);
530                   Cache.log.debug("Finished attempt at storing document.");
531                 }
532                 Cache.log
533                         .debug("finished dealing with REQUESTTOCLOSE event.");
534               }
535             });
536     Cache.log.debug("Added Jalview handler for vamsas document updates.");
537   }
538
539   public void disableGui(boolean b)
540   {
541     Desktop.instance.setVamsasUpdate(b);
542   }
543
544   private boolean joinedSession = false;
545
546   private VamsasListener picker = null;
547
548   private void startSession()
549   {
550     if (inSession())
551     {
552       try
553       {
554         vclient.joinSession();
555         joinedSession = true;
556       } catch (Exception e)
557       {
558         // Complain to GUI
559         Cache.log.error("Failed to join vamsas session.", e);
560         vclient = null;
561       }
562       try
563       {
564         final IPickManager pm = vclient.getPickManager();
565         final StructureSelectionManager ssm = StructureSelectionManager
566                 .getStructureSelectionManager();
567         pm.registerMessageHandler(new IMessageHandler()
568         {
569           String last = null;
570
571           public void handleMessage(Message message)
572           {
573             if (vobj2jv==null)
574             {
575               // we are not in a session yet.
576               return;
577             }
578             if (message instanceof MouseOverMessage)
579             {
580               MouseOverMessage mm = (MouseOverMessage) message;
581               String mstring = mm.getVorbaID() + " " + mm.getPosition();
582               if (last != null && mstring.equals(last))
583               {
584                 return;
585               }
586               // if (Cache.log.isDebugEnabled())
587               // {
588               // Cache.log.debug("Received MouseOverMessage "+mm.getVorbaID()+"
589               // "+mm.getPosition());
590               // }
591               Object jvobj = vobj2jv.get(mm.getVorbaID());
592               if (jvobj != null && jvobj instanceof SequenceI)
593               {
594                 last = mstring;
595                 // Cache.log.debug("Handling Mouse over "+mm.getVorbaID()+"
596                 // bound to "+jvobj+" at "+mm.getPosition());
597                 // position is character position in aligned sequence
598                 ssm.mouseOverVamsasSequence((SequenceI) jvobj, mm
599                         .getPosition());
600               }
601             }
602             if (message instanceof uk.ac.vamsas.client.picking.SelectionMessage)
603             {
604               // we only care about AlignmentSequence selections
605               SelectionMessage sm = (SelectionMessage) message;
606               
607               Object[] jvobjs = new Object[sm.getVorbaIDs().length];
608               Class type = null;
609               for (int o=0;o<jvobjs.length; o++)
610               {
611                 jvobjs[o] = vobj2jv.get(sm.getVorbaIDs()[o]);
612                 if (jvobjs[o]==null)
613                 {
614                   // can't cope with selections for unmapped objects
615                   continue;
616                 }
617                 if (type==null){ type = jvobjs[o].getClass(); };
618                 if (type != jvobjs[o].getClass())
619                 {
620                   // discard - can't cope with selections over mixed objects
621                   continue;
622                 }
623               }
624               if (type == jalview.datamodel.Alignment.class)
625               {
626                 if (jvobjs.length==1) {
627                   // othewise discard - can't cope with multiple alignment selections (at the moment) ?
628                   ;
629                 }
630                 // send a message to select the specified columns over the given alignment
631               }
632               if (type == jalview.datamodel.Sequence.class)
633               {
634                 boolean aligned = ((jalview.datamodel.Sequence)jvobjs[0]).getDatasetSequence()==null;
635                 for (int c=0; jvobjs.length>1 && c<jvobjs.length; c++)
636                 {
637                   if (((jalview.datamodel.Sequence) jvobjs[c]).getDatasetSequence()==null)
638                   {
639                     aligned=false;
640                     continue;
641                   }
642                 }
643                 if (!aligned)
644                 {
645                   // if cardinality is greater than one then verify all sequences are alignment sequences.
646                   if (jvobjs.length==1)
647                   {
648                     // find all instances of this dataset sequence in the displayed alignments containing the associated range and select them. 
649                   } 
650                 } else {
651                   // locate the alignment containing the given sequences and select the associated ranges on them. 
652                 }
653               }
654               // discard message.
655               for (int c=0;c<jvobjs.length;c++) { jvobjs[c] = null;};
656               jvobjs=null;
657               return;
658             }
659           }
660         });
661         picker = new VamsasListener()
662         {
663           SequenceI last = null;
664
665           int i = -1;
666
667           public void mouseOver(SequenceI seq, int index)
668           {
669             if (jv2vobj == null)
670               return;
671             if (seq != last || i != index)
672             {
673               VorbaId v = (VorbaId) jv2vobj.get(seq);
674               if (v != null)
675               {
676                 //Cache.log.debug("Mouse over " + v.getId() + " bound to "
677                 //        + seq + " at " + index);
678                 last = seq;
679                 i = index;
680                 MouseOverMessage message = new MouseOverMessage(v.getId(),
681                         index);
682                 pm.sendMessage(message);
683               }
684             }
685           }
686         };
687         ssm.addStructureViewerListener(picker); // better method here
688       } catch (Exception e)
689       {
690         Cache.log.error("Failed to init Vamsas Picking", e);
691       }
692     }
693   }
694 }