applied LGPLv3 and source code formatting.
[vamsas.git] / src / uk / ac / vamsas / test / ExampleApplication.java
1 /*
2  * This file is part of the Vamsas Client version 0.1. 
3  * Copyright 2009 by Jim Procter, Iain Milne, Pierre Marguerite, 
4  *  Andrew Waterhouse and Dominik Lindner.
5  * 
6  * Earlier versions have also been incorporated into Jalview version 2.4 
7  * since 2008, and TOPALi version 2 since 2007.
8  * 
9  * The Vamsas Client is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *  
14  * The Vamsas Client is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Lesser General Public License for more details.
18  * 
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with the Vamsas Client.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 package uk.ac.vamsas.test;
23
24 import uk.ac.vamsas.client.*;
25 import uk.ac.vamsas.client.picking.IMessageHandler;
26 import uk.ac.vamsas.client.picking.IPickManager;
27 import uk.ac.vamsas.client.picking.Message;
28 import uk.ac.vamsas.objects.core.VAMSAS;
29 import uk.ac.vamsas.test.objects.Core;
30
31 import java.awt.Event;
32 import java.beans.PropertyChangeEvent;
33 import java.beans.PropertyChangeListener;
34 import java.io.DataInput;
35 import java.io.DataInputStream;
36 import java.io.DataOutputStream;
37 import java.io.File;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.io.ObjectInputStream;
41 import java.io.ObjectOutputStream;
42 import java.io.OutputStream;
43 import java.util.Vector;
44
45 /**
46  * Toy vamsas command line client application demonstrating the API. Currently
47  * runs with one argument: ExampleApplication.main(new String("watch")) Test:
48  * Start up at least two of these processes independently and they will
49  * successively modify and handle update events from the document model.
50  * 
51  * Behaviour of each client: Event handlers are registered for documentUpdate. -
52  * every document update: * the vamsas document will be dumped to standard out
53  * using uk.ac.vamsas.test.simpleclient.ArchiveReports * if there are more than
54  * 4 vamsas roots, the first sequence in the first alignment of the first
55  * dataset of the second vamsas root will be appended with a single gap
56  * character and then written back to the session document. A pick thread is
57  * started, which sends random CUSTOM pick events every so often (tuning:
58  * flooding the pick channel seems to affect the behaviour of other vamsasClient
59  * threads, suggesting some object lock contention). A new vamsas root generated
60  * from uk.ac.vamsas.test.objects.Core.getDemoVamsas is added to the document.
61  * Then a while loop waits around for events until shutdown: - currently it will
62  * shutdown after 9 updates (totalUpdates) - an update will be made after every
63  * other update that is detected.
64  * 
65  * Status: PickManager now shuts down correctly. Serial updates for two
66  * instances work correctly and are detected under j1.4 (need to test in 1.5 and
67  * 1.6).
68  * 
69  * TODO: test appData get/set methods
70  * 
71  * TODO: verify and test pickManager and interaction between it and other
72  * session events
73  * 
74  * TODO: add more session interaction events
75  * 
76  * TODO: test client add/leave events - currently library generates exceptions
77  * for sessionlist and clientlist modifications.
78  * 
79  * @author jimp
80  */
81
82 public class ExampleApplication {
83   private ClientHandle app;
84
85   private UserHandle user; // TODO: make this something defined by the
86
87   // api
88
89   private IClientFactory clientfactory;
90
91   private IClient vorbaclient;
92
93   private byte[] mydata;
94
95   private Vector vamsasObjects;
96
97   private boolean isUpdated = false;
98
99   private boolean isShuttingdown = false;
100
101   private boolean isFinalizing = false;
102
103   private int totalUpdates = 9;
104
105   private uk.ac.vamsas.client.VorbaId recover = null;
106
107   private int calls = 0;
108
109   private long mdatahash = 0;
110
111   private long muserdatahash = 0;
112
113   private void processVamsasDocument(IClientDocument doc) {
114     if (doc.getVamsasRoots().length < 4) {
115       doc.addVamsasRoot(Core.getDemoVamsas());
116     } else {
117       try {
118         uk.ac.vamsas.objects.core.DataSet ds = doc.getVamsasRoots()[1]
119             .getDataSet(0);
120         uk.ac.vamsas.objects.core.AlignmentSequence alsq = ds.getAlignment(0)
121             .getAlignmentSequence(0);
122         if (recover == null) {
123           recover = alsq.getVorbaId();
124         } else {
125           Vobject recoverd = doc.getObject(recover);
126           System.out.println("Recovery of " + recover + " was "
127               + ((recoverd == null) ? "A FAILURE" : "SUCCESSFUL"));
128         }
129         System.out.println("Modifying Sequence:\n" + alsq.hashCode());
130         alsq.setSequence(alsq.getSequence() + ds.getAlignment(0).getGapChar());
131         System.out.println("Modifying Sequence:\n" + alsq.hashCode());
132         System.out.println("Modified Sequence:\n" + alsq.getSequence());
133         doc.setVamsasRoots(doc.getVamsasRoots());
134       } catch (Exception ee) {
135
136       }
137     }
138     // get this apps 'mydata' if it hasn't got it already.
139     System.out.println("Trying to get appdata and modify it.....");
140     try {
141       processAppData(doc);
142       System.out.println(".....Finished.");
143     } catch (Exception e) {
144       System.err.println("Failed to process appdata for our application.");
145       e.printStackTrace(System.err);
146     }
147
148     // .. access this application's 'public' mydata' if there is any.
149     vorbaclient.updateDocument(doc);
150     // merge vamsasObjects with vamsas objects in document
151
152   }
153
154   private int appdatareads = 0;
155
156   boolean clientappd = true;
157
158   boolean userappd = false;
159
160   private void processAppData(IClientDocument doc) throws Exception {
161     appdatareads++;
162     boolean writtenonce = false;
163     if (doc != null) {
164       uk.ac.vamsas.client.IClientAppdata appd = doc.getClientAppdata();
165       if (clientappd) {
166         if (appd.hasClientAppdata() && !(appdatareads % 2 == 0)) {
167           // byte[] cappd = appd.getClientAppdata();
168           // if (cappd!=null)
169           // System.out.println("Client appdata\n"+cappd.toString()+"\nEnd of
170           // Appdata\n");
171           System.out.println("Testing read from inputstream");
172           String cappds = readData(appd.getClientInputStream());
173           System.out
174               .println("Client appdata\n" + cappds + "\nEnd of Appdata\n");
175         } else {
176           if (!writtenonce) {
177             String newapd = "Client Appdata:";
178             if (appd.hasClientAppdata()) {
179               AppDataInputStream is;
180               newapd = readData(is = appd.getClientInputStream());
181               is.close();
182             }
183             writtenonce = true;
184             // appd.setClientAppdata(makeappData("Client Appdata for
185             // "+user.toString()+" written"));
186             writeData(appd.getClientOutputStream(), newapd
187                 + " : Client Appdata for all users written on " + appdatareads
188                 + " read by " + vorbaclient.getUserHandle());
189             System.out.println("Written to ClientAppdata stream.");
190           }
191         }
192       }
193       if (userappd) {
194         if (appd.hasUserAppdata() && !(appdatareads % 2 == 0)) {
195           byte[] cappd = appd.getUserAppdata();
196           if (cappd != null)
197             System.out.println("User appdata\n" + new String(cappd)
198                 + "\nEnd of Users' Appdata\n");
199           else {
200             System.out.println("No user appdata.");
201             appd
202                 .setUserAppdata(("no default - overwritten null byte set on "
203                     + appdatareads + " read by " + vorbaclient.getUserHandle() + "")
204                     .getBytes());
205           }
206         } else if (!writtenonce) {
207           writtenonce = true;
208           byte[] bts = makeappData("User Appdata for " + user + " written on "
209               + appdatareads + " read at ");
210           System.out.println("Setting appData bytes to\n" + new String(bts)
211               + "\nEnd.");
212           appd.setUserAppdata(bts);
213           System.out.println("Written to UserAppdata stream.");
214         }
215       }
216     }
217   }
218
219   private byte[] makeappData(String message) {
220     StringBuffer sb = new StringBuffer();
221     sb.append(message);
222     sb.append("on " + new java.util.Date());
223     return sb.toString().getBytes();
224   }
225
226   private boolean writeData(AppDataOutputStream os, String message) {
227     StringBuffer sb = new StringBuffer();
228     sb.append(message);
229     sb.append("on " + new java.util.Date());
230     try {
231       ObjectOutputStream oos = new ObjectOutputStream(os);
232       oos.writeObject(sb.toString());
233       oos.flush();
234       oos.close();
235     } catch (Exception e) {
236       System.err.println("Problem serialising this message:\n" + sb);
237       e.printStackTrace(System.err);
238       return false;
239     }
240     return true;
241   }
242
243   private String readData(AppDataInputStream is) {
244     if (is != null) {
245       try {
246         if (is.available() > 0) {
247           ObjectInputStream ois = new ObjectInputStream(is);
248           String rs = (String) ois.readObject();
249           return rs;
250         }
251       } catch (Exception e) {
252         System.err.println("Failed to read a string from input stream!");
253         e.printStackTrace(System.err);
254       }
255     }
256     return "";
257   }
258
259   private void addHandlers(IClient avorbaclient) {
260     final ExampleApplication me = this;
261     // make a non-volatile reference to the client instance.
262     final IClient vorbaclient = avorbaclient;
263     // register update handler
264     vorbaclient.addDocumentUpdateHandler(new PropertyChangeListener() {
265       public void propertyChange(PropertyChangeEvent evt) {
266         System.out.println("Vamsas document update for "
267             + evt.getPropertyName() + ": " + evt.getOldValue() + " to "
268             + evt.getNewValue());
269         // merge new data into ours.
270         // example - output doc
271         try {
272           IClientDocument cdoc = vorbaclient.getClientDocument();
273           if (calls > 2 && cdoc.getVamsasRoots().length > 0
274               && !cdoc.getVamsasRoots()[0].is__stored_in_document()) {
275             System.err
276                 .println("Pathological Update Detected - Document is zeroed!");
277           }
278           calls++;
279           uk.ac.vamsas.test.simpleclient.ArchiveReports.rootReport(cdoc
280               .getVamsasRoots(), true, System.out);
281           // Simple update
282           try {
283             if (cdoc.getVamsasRoots().length > 2) {
284               uk.ac.vamsas.objects.core.DataSet ds = cdoc.getVamsasRoots()[1]
285                   .getDataSet(0);
286               uk.ac.vamsas.objects.core.AlignmentSequence alsq = ds
287                   .getAlignment(0).getAlignmentSequence(0);
288               if (alsq.isUpdated())
289                 System.out.println("Seqeuence was updated since last time.");
290               alsq.setSequence(alsq.getSequence()
291                   + ds.getAlignment(0).getGapChar());
292               System.out.println("Newly Modified Sequence:\n"
293                   + alsq.getSequence());
294               cdoc.setVamsasRoots(cdoc.getVamsasRoots());
295             }
296           } catch (Exception ee) {
297             System.err.println("Exception whilst updating :");
298             ee.printStackTrace(System.err);
299           }
300           vorbaclient.updateDocument(cdoc);
301         } catch (Exception e) {
302           System.err
303               .println("Exception whilst dumping document tree after an update.");
304           e.printStackTrace(System.err);
305         }
306         isUpdated = true; // tell main thread to reflect change...
307       }
308     });
309     // register close handler
310     vorbaclient.addVorbaEventHandler(Events.DOCUMENT_REQUESTTOCLOSE,
311         new PropertyChangeListener() {
312           public void propertyChange(PropertyChangeEvent evt) {
313             System.out.println("Received request to close vamsas document.");
314             // ask user for a filename to save it to.
315             // Then pass it to the vorba object...
316             vorbaclient.storeDocument(new java.io.File("UserLocation"));
317           }
318         });
319
320     // register some more handlers to monitor the session :
321
322     vorbaclient.addVorbaEventHandler(Events.CLIENT_CREATION,
323         new PropertyChangeListener() {
324           public void propertyChange(PropertyChangeEvent evt) {
325             System.out.println("New Vamsas client for " + evt.getPropertyName()
326                 + ": " + evt.getOldValue() + " to " + evt.getNewValue());
327             // tell app add new client to its list of clients.
328           }
329         });
330     vorbaclient.addVorbaEventHandler(Events.CLIENT_FINALIZATION,
331         new PropertyChangeListener() {
332           public void propertyChange(PropertyChangeEvent evt) {
333             System.out.println("Vamsas client finalizing for "
334                 + evt.getPropertyName() + ": " + evt.getOldValue() + " to "
335                 + evt.getNewValue());
336             // tell app to update its list of clients to communicate
337             // with.
338           }
339         });
340     vorbaclient.addVorbaEventHandler(Events.SESSION_SHUTDOWN,
341         new PropertyChangeListener() {
342           public void propertyChange(PropertyChangeEvent evt) {
343             System.out.println("Session " + evt.getPropertyName()
344                 + " is shutting down.");
345             // tell app to finalize its session data before
346             // shutdown.
347           }
348         });
349     vorbaclient.addVorbaEventHandler(Events.DOCUMENT_FINALIZEAPPDATA,
350         new PropertyChangeListener() {
351           public void propertyChange(PropertyChangeEvent evt) {
352             boolean finalized = false;
353             System.out
354                 .println("Application received a DOCUMENT_FINALIZEAPPDATA event.");
355             IClientDocument cdoc = null;
356             try {
357               cdoc = vorbaclient.getClientDocument();
358               if (cdoc != null) {
359                 IClientAppdata apd = cdoc.getClientAppdata();
360                 if (apd != null) {
361                   String userd = "";
362                   if (apd.getUserAppdata() != null) {
363                     userd = new String(apd.getUserAppdata());
364                   }
365                   String appdat = me.readData(apd.getClientInputStream());
366                   me.writeData(apd.getClientOutputStream(), appdat
367                       + "\nUser Data merged\n" + userd + "\n");
368                 }
369                 finalized = true;
370                 vorbaclient.updateDocument(cdoc);
371               }
372               // tell app to finalize its session data prior to the
373               // storage of the current session as an archive.
374             } catch (Exception e) {
375               if (!finalized) {
376                 System.err
377                     .println("Probable library problem - exception when trying to update document after writing merged appdata.");
378                 e.printStackTrace(System.err);
379               } else {
380                 System.err
381                     .println("Recovering from exception when writing merged appdata");
382                 e.printStackTrace(System.err);
383                 try {
384                   if (cdoc != null) {
385                     vorbaclient.updateDocument(cdoc);
386                   }
387                 } catch (Exception e2) {
388                   System.err
389                       .println("Probable library problem - exception when trying to update document after an exception when writing merged appdata.");
390                   e2.printStackTrace(System.err);
391                 }
392               }
393               return;
394             }
395             System.out.println("Application finalized appdata successfuly.");
396           }
397
398         });
399   }
400
401   public String Usage = "ExampleApplication :/n [-arena <vamsasFileDirectory>][-session <vamsasSessionURN>] <action> [+<arguments>]\n"
402       + "<action> is one of :\n\tsave,new,watch\n"
403       + "-arena and -session are not yet supported"; // TODO for release
404
405   String sess = null;
406
407   String importFile = null;
408
409   String outputFile = null;
410
411   boolean newSession = false;
412
413   boolean imported = false;
414
415   private boolean parseArgs(String args[]) {
416     if (args.length == 0) {
417       return false;
418     }
419     int cpos = 0;
420     boolean parsed = false;
421     while (!parsed && cpos < args.length) {
422       // TODO: note importDocument not yet supported.
423       // if (args[cpos].toLowerCase().equals("load") && cpos + 1 < args.length)
424       // {
425       // importFile = args[cpos + 1];
426       // cpos += 2;
427       // continue;
428       // }
429       if (args[cpos].toLowerCase().equals("new") && cpos + 1 < args.length) {
430         importFile = args[cpos + 1];
431         newSession = true;
432         cpos += 2;
433         continue;
434       }
435       if (args[cpos].toLowerCase().equals("save") && cpos + 1 < args.length) {
436         outputFile = args[cpos + 1];
437         cpos += 2;
438         continue;
439       }
440       // default behaviour - if not anything else its probably a session urn
441       if (!args[cpos].toLowerCase().equals("watch")) {
442         sess = args[cpos];
443       }
444       cpos++;
445     }
446     return true;
447   }
448
449   class ExamplePicker extends Thread {
450     String me = null;
451
452     public IPickManager pm = null;
453
454     ExamplePicker(String me, IPickManager pm) {
455       this.me = me;
456       this.pm = pm;
457     }
458
459     public void run() {
460       int mcount = 1;
461       while (pm != null) {
462         try {
463           Thread.sleep(1000 + (long) Math.random() * 10000);
464         } catch (Exception e) {
465         }
466
467         if (pm != null) {
468           pm.sendMessage(new uk.ac.vamsas.client.picking.CustomMessage(
469               "Message " + mcount++ + " from " + me));
470         }
471       }
472     }
473
474   }
475
476   long shutdown = -1;
477
478   public void runMe(String[] args) {
479     if (!parseArgs(args)) {
480       System.err.print(Usage);
481     }
482     // get IClientFactory
483     try {
484       clientfactory = new uk.ac.vamsas.client.simpleclient.SimpleClientFactory();
485     } catch (IOException e) {
486       System.err.println(e + "\n" + Usage);
487       System.exit(1);
488     }
489
490     // get an Iclient with session data
491     app = new ClientHandle("uk.ac.vamsas.test.ExampleApplication", "0.1");
492     user = new UserHandle("arnolduser", "deathsdoor");
493     if (sess != null && importFile != null) {
494       System.err
495           .println("Import of an existing document into an existing session is not fully supported yet.");
496       System.exit(3);
497     }
498     try {
499       if (sess != null) {
500         System.out.println("Connecting to " + sess);
501         vorbaclient = clientfactory.getIClient(app, user, sess);
502       } else {
503         if (newSession) {
504           if (importFile == null) {
505             System.out.println("Connecting to a new VAMSAS Session.");
506             vorbaclient = clientfactory.getNewSessionIClient(app, user);
507           } else {
508             System.out
509                 .println("Connecting to a new VAMSAS Session to share data in existing document:"
510                     + importFile);
511             File vfile = new File(importFile);
512             vorbaclient = clientfactory.openAsNewSessionIClient(app, user,
513                 vfile);
514             System.out
515                 .println("Succesfully imported document into new session.");
516             imported = true;
517             importFile = null;
518           }
519         } else {
520           vorbaclient = clientfactory.getIClient(app, user);
521         }
522       }
523     } catch (InvalidSessionDocumentException e) {
524       System.err.println("Failed to create a session to share " + importFile);
525       System.err.println("Sorry it didn't work out. This is what went wrong:");
526       e.printStackTrace(System.err);
527       System.exit(3);
528     } catch (NoDefaultSessionException e) {
529       System.err
530           .println("There appear to be several sessions to choose from :");
531       String[] sessions = clientfactory.getCurrentSessions();
532       for (int s = 0; s < sessions.length; s++)
533         System.err.println(sessions[s]);
534       System.exit(2);
535     }
536     addHandlers(vorbaclient);
537     try {
538       vorbaclient.joinSession();
539     } catch (Exception se) {
540       se.printStackTrace();
541       System.err.println(se + " when joining session.\n" + Usage);
542       System.exit(1);
543     }
544     // register an update listener and a close listener.
545     // import any data if requested to
546     if (importFile != null) {
547       File vfile = new File(importFile);
548       try {
549         vorbaclient.importDocument(vfile);
550         imported = true;
551       } catch (Exception e) {
552         System.err.println("Failed to import file " + importFile);
553         System.err.println("Exception received was " + e);
554         e.printStackTrace(System.err);
555       }
556
557     }
558     // Write out any data if requested to
559     if (outputFile != null) {
560       File vfile = new File(outputFile);
561       try {
562         vorbaclient.storeDocument(vfile);
563       } catch (Exception e) {
564         System.err.println("Failed to export session as file " + outputFile);
565         System.err.println("Exception received was " + e);
566         e.printStackTrace(System.err);
567       }
568     }
569     // get document data
570     try {
571       IClientDocument cdoc = vorbaclient.getClientDocument();
572       if (imported) {
573         if (cdoc.getVamsasRoots() == null
574             || cdoc.getVamsasRoots()[0].getDataSetCount() < 1) {
575           System.err
576               .println("Imported an Empty vamsas document. Is this correct ?");
577         }
578       }
579       processVamsasDocument(cdoc);
580     } catch (Exception e) {
581       System.err
582           .println("Unexpected exception when retrieving the client document for the first time!");
583       e.printStackTrace(System.err);
584       System.exit(1);
585     }
586     int update = 0;
587     ExamplePicker picker = new ExamplePicker(vorbaclient.getClientHandle()
588         .getClientUrn(), vorbaclient.getPickManager());
589
590     picker.start();
591     if (picker.pm != null) {
592       picker.pm.registerMessageHandler(new IMessageHandler() {
593
594         public void handleMessage(Message message) {
595           System.out.println("Received |" + message.getRawMessage() + "|");
596           shutdown += 100; // hang around for 5 seconds or so before dying
597           // naturally.
598
599         }
600
601       });
602     }
603     shutdown = System.currentTimeMillis() + 10000; // hang around for 10
604     // seconds or so before
605     // dying naturally.
606     while (!isShuttingdown && update < totalUpdates) {
607       // do something with data
608       // , update document, or something.
609       // ..
610       if (isUpdated) {
611         System.out.println("Update handler called " + (++update) + " times");
612         System.out.println("******************************************");
613         isUpdated = false; // TODO: saner update det method.
614         shutdown = System.currentTimeMillis() + 10000;
615         if (update % 2 == 1) {
616           try {
617             IClientDocument cdoc = vorbaclient.getClientDocument();
618             processVamsasDocument(cdoc);
619           } catch (Exception e) {
620             System.err
621                 .println("Error when updating document after an even numbered update.");
622             e.printStackTrace(System.err);
623           }
624           try {
625             Thread.sleep(200);
626           } catch (Exception e) {
627           }
628         }
629       } else {
630         if (System.currentTimeMillis() > shutdown) {
631           isShuttingdown = true;
632         }
633       }
634
635       try {
636         Thread.sleep(50);
637       } catch (Exception e) {
638       }
639
640     }
641     System.out.println("Finalizing.");
642     // call finalizeClient
643     vorbaclient.finalizeClient();
644     System.out.println("Shutting down picker.");
645     picker.pm = null;
646     while (picker.isAlive()) {
647       System.out.println("Waiting for picker to die...");
648       try {
649         Thread.sleep(1000);
650       } catch (Exception e) {
651       }
652       ;
653     }
654
655     // { meanwhile, eventHandlers are called to do any saves if need be }
656     // and all registered listeners will be deregistered to avoid deadlock.
657
658     // finish
659     vorbaclient = null;
660     clientfactory = null;
661     System.out.println("Byee!");
662     // try { Thread.sleep(100000); } catch (Exception e) {}
663   }
664
665   public static void main(String[] args) {
666     ExampleApplication example = new ExampleApplication();
667     example.runMe(args);
668   }
669 }