SimpleClient implementation testable against uk.ac.vamsas.test.ExampleApplication
[vamsas.git] / src / uk / ac / vamsas / client / simpleclient / SimpleClient.java
1 /*
2  * Created on 15-Sep-2005
3  *
4  * TODO To change the template for this generated file go to
5  * Window - Preferences - Java - Code Style - Code Templates
6  */
7 package uk.ac.vamsas.client.simpleclient;
8
9 import java.beans.EventHandler;
10 import java.beans.PropertyChangeEvent;
11 import java.beans.PropertyChangeListener;
12 import java.beans.PropertyChangeSupport;
13 import java.io.BufferedReader;
14 import java.io.File;
15 import java.io.IOException;
16 import java.net.MalformedURLException;
17 import java.util.Hashtable;
18 import java.util.Vector;
19
20 import org.apache.commons.logging.Log;
21 import org.apache.commons.logging.LogFactory;
22
23 import uk.ac.vamsas.client.ClientHandle;
24 import uk.ac.vamsas.client.Events;
25 import uk.ac.vamsas.client.IClient;
26 import uk.ac.vamsas.client.IClientDocument;
27 import uk.ac.vamsas.client.IObjectUpdate;
28 import uk.ac.vamsas.client.InvalidSessionUrnException;
29 import uk.ac.vamsas.client.SessionHandle;
30 import uk.ac.vamsas.client.UserHandle;
31 import uk.ac.vamsas.client.picking.IPickManager;
32 import uk.ac.vamsas.objects.core.ApplicationData;
33 import uk.ac.vamsas.objects.core.Entry;
34 import uk.ac.vamsas.objects.core.LockFile;
35 import uk.ac.vamsas.objects.core.VamsasDocument;
36 import uk.ac.vamsas.objects.utils.AppDataReference;
37 import uk.ac.vamsas.objects.utils.ProvenanceStuff;
38 import uk.ac.vamsas.objects.utils.document.VersionEntries;
39
40 /**
41  * @author jimp
42  */
43 public class SimpleClient implements IClient {
44   
45   private static Log log = LogFactory.getLog(SimpleClient.class);
46   
47   protected UserHandle user = null;
48   
49   protected SessionUrn session = null;
50   protected VamsasSession _session;
51   protected ClientHandle client = null;
52   protected EventGeneratorThread evgen = null;
53   protected ClientDocument cdocument = null;
54   /**
55    * object hash table that persists in each client holding vorbaIds and hash values after a document write
56    */
57   protected Hashtable extantobjects=null;
58   /**
59    * construct a transient IdFactory instance - this should last only as long as the 
60    * SimpleClient object holds the lock on the vamsas document being created/manipulated.
61    * @return
62    */
63   private IdFactory makeVorbaIdFactory() {
64     return new IdFactory(getSessionHandle(), client, user);
65   }
66   
67   /**
68    * construct SimpleClient for user, client and VamsasSession directory
69    * use the SimpleClientFactory rather than this constructor directly. 
70    * @param user
71    * @param client
72    * @param sess
73    */
74   protected SimpleClient(UserHandle user, ClientHandle client, VamsasSession sess) throws InvalidSessionUrnException {
75     // TODO: validate user/client/session
76     _session = sess;
77     this.user = user;
78     this.client = client;
79     //try {
80     log.debug("Creating new session for "+_session);
81       session = new SessionUrn(_session);
82       log.debug("Creating new Event Generator");
83       evgen = new EventGeneratorThread(_session, this, handlers);
84       /*} catch (MalformedURLException e) {
85       log.error("Couldn't form a valid SessionUrn object!",e);
86       throw new InvalidSessionUrnException(_session.toString());
87     }*/
88       log.debug("SimpleClient constructed for session "+session.getSessionUrn());
89       
90   }
91   /**
92    * construct new session by importing objects from an existing vamsas document
93    * @param user
94    * @param client
95    * @param sess
96    * @param importingArchive
97    * @throws Exception IOExceptions for Session IO problems, and general Exception if importing document is invalid.
98    */
99   protected SimpleClient(UserHandle user, ClientHandle client, VamsasSession sess, File importingArchive) throws Exception {
100     this(user, client, sess);
101     VamsasArchive sessdoc = _session.getVamsasDocument();
102     try {
103       VamsasArchiveReader odoc = new VamsasArchiveReader(importingArchive);
104       SimpleDocument sdoc = new SimpleDocument(makeVorbaIdFactory());
105       VamsasDocument doc = sdoc.getVamsasDocument(odoc);
106       sessdoc.putVamsasDocument(doc, sdoc.vorba);
107       sessdoc.closeArchive();
108       log.debug("Imported new vamsas data from "+importingArchive);
109     } catch (Exception e) {
110       sessdoc.cancelArchive();
111       // write a dummy iohandler
112       _session.slog.info("Exception when importing document data from "+importingArchive);
113       log.warn("While importing session data from existing archive in "+importingArchive, e);      
114       throw new Exception("Failed to import data from "+importingArchive, e);
115     }
116   }
117   
118   /*
119    * (non-Javadoc)
120    * LATER: check that build substitution variables are correct
121    * @see uk.ac.vamsas.client.IClient#getAbout()
122    */
123   public String getAbout() {
124     return new String("VORBA SimpleClient version $version$ build $build$");
125   }
126   
127   /*
128    * (non-Javadoc)
129    * 
130    * @see uk.ac.vamsas.client.IClient#getSessionUrn()
131    */
132   public String getSessionUrn() {
133     return session.getSessionUrn();
134   }
135   
136   /*
137    * (non-Javadoc)
138    * 
139    * @see uk.ac.vamsas.client.IClient#getSessionHandle()
140    */
141   public SessionHandle getSessionHandle() {
142     // TODO: eliminate SessionHandle ? need to refactor interfaces.
143     SessionHandle sh = new SessionHandle(session.getSessionUrn());
144     return sh;
145   }
146   
147   /*
148    * (non-Javadoc)
149    * 
150    * @see uk.ac.vamsas.client.IClient#getClientHandle()
151    */
152   public ClientHandle getClientHandle() {
153     return client;
154   }
155   
156   /*
157    * (non-Javadoc)
158    * 
159    * @see uk.ac.vamsas.client.IClient#getUserHandle()
160    */
161   public UserHandle getUserHandle() {
162     return user;
163   }
164   /**
165    * 
166    * @return user field for a provenance entry
167    */
168   protected String getProvenanceUser() {
169     return new String(user.getFullName());
170   }
171   /**
172    * construct a provenance entry for this client with the specified action string.
173    * @param action
174    * @return properly completed provenance entry
175    */
176   protected Entry getProvenanceEntry(String action) {
177     Entry prov = ProvenanceStuff.newProvenanceEntry(client.getClientUrn(), getProvenanceUser(), action);
178     return prov;
179   }
180   private Hashtable handlers = initHandlers();
181   
182   private Vector listeners = new Vector();
183   
184   /**
185    * make all the PropertyChangeSupport objects for the
186    * events described in uk.ac.vamsas.client.Event
187    * @return
188    */
189   private static Hashtable initHandlers() {
190     Hashtable events = new Hashtable();
191     java.util.Iterator evt = Events.EventList.iterator();
192     while (evt.hasNext()) {
193       Object ths = evt.next();
194       events.put(ths, (Object) new PropertyChangeSupport(ths));
195     }
196     return events;
197   }
198   
199   /*
200    * (non-Javadoc)
201    * 
202    * @see uk.ac.vamsas.client.IClient#addDocumentUpdateHandler(java.util.EventListener)
203    */
204   public void addDocumentUpdateHandler(PropertyChangeListener evt) {
205     this.addVorbaEventHandler(Events.DOCUMENT_UPDATE, evt);
206   }
207   boolean finalized=false;
208   private void haltPickmanager() {
209     if (pickmanager!=null) {
210       final SimpleClient dying=this; 
211       new Thread() {
212         public void run() {
213           SimpleClient.log.debug("Stopping pickManager..");
214           dying.pickmanager.haltPickManager();
215           SimpleClient.log.debug("pickManager halted.");
216         }
217       }.start();
218     }
219   }
220   /*
221    * (non-Javadoc)
222    * 
223    * @see uk.ac.vamsas.client.IClient#finalizeClient()
224    */
225   public void finalizeClient() {
226     if (finalized)
227       throw new Error("VAMSAS Client Implementation Error: Finalized called twice for same client instance.");
228
229     // mark this instance as finalized
230     finalized=true;
231     
232     // TODO: determine if this is last client in session
233     
234     // TODO: raise events like : ((lst_client && document.request.to.close), (client_finalization), (
235     evgen._raise(Events.CLIENT_FINALIZATION, null, this,null);
236     // if (handlers.containsKey(Events.))
237     // if (handlers.containsKey(Events.CLIENT_FINALIZATION))
238     // deregister listeners.
239     haltPickmanager();
240     SimpleClient.log.debug("Stopping EventGenerator..");
241     evgen.stopWatching();
242     SimpleClient.log.debug("EventGenerator halted.");
243     log.debug("Deregistering Client");
244     _session.removeClient(this);
245     log.debug("finalization Complete.");
246   }
247   
248   /*
249    * (non-Javadoc)
250    * 
251    * @see uk.ac.vamsas.client.IClient#getClientDocument()
252    */
253   public IClientDocument getClientDocument() throws IOException {
254     log.debug("getClientDocument");
255     if (cdocument!=null) {
256       // cdocument is non-nill if the ClientDocument.finalise() method hasn't been called.
257       return cdocument;
258     }
259     evgen.disableDocumentWatch();
260     VamsasArchive va = null;
261     try {
262       // LATER: bail out if it takes too long to get the lock ?
263       va = _session.getVamsasDocument();
264     }
265     catch (IOException e) {
266       throw new IOException("Failed to get lock on session document");
267     }
268     VamsasDocument doc=null;
269     IdFactory vorba = null;
270     // TODO: LATER: reduce size of vorba ids generated from these parameters to IdFactory (mainly sessionHandle rationalization ?)
271     try {
272       va.setVorba(vorba=makeVorbaIdFactory());
273       // if session currently holds data - read it in - or get a dummy
274       log.debug("Accessing document");
275       doc = 
276         va.getVamsasDocument(getProvenanceUser(),
277             "created new session document.", null);
278       if (doc!=null)
279         log.debug("Successfully retrieved document.");
280       else
281         log.error("Unexpectedly retrieved null document!");
282     }
283     catch (Exception e) {
284       log.error("Failed to get session document for session directory '"+_session.sessionDir+"'", e);
285       throw new IOException("Failed to get session document for session directory '"+_session.sessionDir+"'");
286     }
287     // Construct the IClientDocument instance
288     
289     cdocument = new ClientDocument(doc, va, vorba, this);
290     return cdocument;
291   }
292   
293   /*
294    * (non-Javadoc)
295    * @throws Errors for invalid newdoc parameter
296    * @see uk.ac.vamsas.client.IClient#updateDocument(uk.ac.vamsas.client.IClientDocument)
297    */
298   public void updateDocument(IClientDocument newdoc) {
299     log.debug("updateDocument:");
300     // Check validity of simpleclient instance and that it holds a lock on the session's document
301     if (!(newdoc instanceof ClientDocument)) {
302       throw new Error("Invalid IClientDocument passsed to SimpleClient.");
303     }
304     if (cdocument==null)
305       throw new Error("Client Error - updateDocument() called before getClientDocument() on this SimpleClient instance.");
306     if (newdoc!=cdocument)
307       throw new Error("Client Error - SimpleClient.updateDocument() can only take the IClientDocument instance returned from SimpleClient.getClientDocument()");
308
309     if (evgen.isDocumentWatchEnabled())
310       throw new Error("Client Error - or Library Bug : Document watcher still enabled whilst ClientDocument instance exists.");
311     
312     if (!cdocument.isModified()) {
313       // client document is silently got rid of, with no session update events.
314       if (log.isDebugEnabled())
315         log.debug("updateDocument for "+session.getSessionUrn()+" with unmodified IClientDocument (skipping the write)");
316     } else {
317       try {
318         if (!cdocument.updateSessionDocument()) {
319           log.warn("Session document did not update properly for session directory "+_session.sessionDir);
320           // cdocument.archive.cancelArchive(); // LATER: could be done - would need to prevent updateSessionDocument closing the iohandler.
321           _session.slog.warn("Session Document updated but may not be valid (false return from org.vamsas.simpleclient.ClientDocument.updateSessionDocument()");
322         }
323         log.debug("Document update successful.");
324       }
325       catch (IOException e) {
326         log.warn("IO Problems when updating document!",e);
327         _session.slog.error("IO problems when attempting to update document.");
328       }
329     }
330     try {
331       _session.setUnsavedFlag();
332       _session.unlockVamsasDocument();
333       evgen.enableDocumentWatch();
334     } catch (IOException e) {
335       log.warn("IO Problems when releasing lock on session document!",e);
336       _session.slog.error("IO problems when attempting to release lock on session document.");
337     }
338     // garbage collect the ClientDocument instance.
339     try {
340       log.debug("Finalizing ClientDocument instance.");
341       cdocument.finalize();
342     } catch (Throwable e) {
343       log.error("Exception when trying to garbage collect ClientDocument for "+session.getSessionUrn(), e);
344     }
345     cdocument = null; // this is probably done by finalize
346   }
347   
348   /*
349    * (non-Javadoc)
350    * 
351    * @see uk.ac.vamsas.client.IClient#storeDocument(java.io.File)
352    */
353   public void storeDocument(File location) {
354     if (location==null)
355       throw new Error("Vamsas Client API Usage Error: storeDocument called with null location.");
356     log.debug("StoreDocument to "+location);
357     // write storeDocument file to inform other clients that they should raise
358     Lock vamlock = evgen.want_to_store();
359     // Events.DOCUMENT_FINALIZEAPPDATA
360     try {
361       _session.writeVamsasDocument(location, vamlock);
362       _session.clearUnsavedFlag();
363     } catch (Exception e) {
364       log.warn("Exception whilst trying to store document in "+location,e);
365     }
366     
367     vamlock.release();
368   }
369   
370   /*
371    * (non-Javadoc)
372    * 
373    * @see uk.ac.vamsas.client.IClient#addVorbaEventHandler(java.lang.String,
374    *      java.beans.PropertyChangeListener)
375    */
376   public void addVorbaEventHandler(String EventChain, PropertyChangeListener evt) {
377     if (handlers.containsKey(EventChain)) {
378       log.debug("Adding new handler for "+EventChain);
379       Object handler;
380       ((PropertyChangeSupport) (handler = handlers.get(EventChain)))
381       .addPropertyChangeListener(evt);
382       listeners.add(handler);
383       listeners.add((Object) evt);
384     }
385   }
386   
387   /* (non-Javadoc)
388    * @see uk.ac.vamsas.client.IClient#pollUpdate()
389    */
390   public void pollUpdate() {
391     log.debug("pollUpdate");
392     if (evgen==null) {
393       log.warn("pollUpdate called on incomplete SimpleClient object.");
394       return;
395     }
396     
397     if (!evgen.isWatcherAlive()) {
398       log.warn("pollUpdate called before joinSession() - trying to do this.");
399       try {
400         joinSession();
401       } catch (Exception e) {
402         log.error("Unexpected exception on default call to joinSession",e);
403       }
404     }
405     
406     //TODO ensure event generator robustly handles these interrupts.
407     log.debug("interrrupting event generator.");
408     evgen.interruptWatching();
409     log.debug("interrrupted event generator.");
410   }
411   
412   /* (non-Javadoc)
413    * @see uk.ac.vamsas.client.IClient#joinSession()
414    */
415   public void joinSession() throws Exception {
416     log.debug("Joining Session.");
417     // start the EventGenerator thread.
418     if (evgen==null) {
419       log.warn("joinSession called on incomplete SimpleClient object.");
420       return;
421     }
422     if (evgen.isWatcherAlive())
423       throw new Error("Join session called twice for the same SimpleClient (IClient instance).");
424     evgen.startWatching();
425     if (evgen.isWatcherAlive())
426       log.debug("Started EventGenerator thread.");
427     else {
428       log.warn("Failed to start EventGenerator thread.");
429       throw new Exception("Failed to start event generator thread - client cannot be instantiated.");
430     }
431     if (evgen.countHandlersFor(Events.DOCUMENT_CREATE)>0) {
432       //TODO: LATER: is this application connecting to a newly created session document ?
433       //evgen.raise(Events.DOCUMENT_CREATE);
434     }
435     
436   }
437   
438   
439   
440   /* (non-Javadoc)
441    * @see uk.ac.vamsas.client.IClient#importDocument(java.io.File)
442    */
443   public void importDocument(File location) {
444     // TODO LATER: implement SimpleClient.importDocument()
445     log.error("importDocument is not yet implemented for a SimpleClient Session.");
446   }
447
448   public IObjectUpdate getUpdateHandler(Class rootObject) {
449     // TODO Auto-generated method stub
450     return null;
451   }
452
453   public IObjectUpdate[] getUpdateHandlers() {
454     // TODO Auto-generated method stub
455     return null;
456   }
457
458   public void removeUpdateHandler(Class rootObject) {
459     // TODO Auto-generated method stub
460     
461   }
462
463   public void setUpdateHandler(IObjectUpdate handler) {
464     // TODO Auto-generated method stub
465     
466   }
467
468   /**
469    * retrieves the current VamsasSession to which belong the client
470    * @return the _session
471    */
472   protected VamsasSession get_session() {
473     return this._session;
474   }
475   SimplePickManager pickmanager=null;
476   /* (non-Javadoc)
477    * @see uk.ac.vamsas.client.IClient#getPickManager()
478    */
479   public IPickManager getPickManager() {
480     createPickManager();
481     return pickmanager;
482   }
483
484   private void createPickManager() {
485     if (pickmanager==null){
486       // TODO: Construct PickManager for session using details from sessionURN!
487       log.debug("Creating PickManager (not from sessionURN yet)");
488       pickmanager = new SimplePickManager(new uk.ac.vamsas.client.picking.SocketManager());
489     }
490   }
491 }