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