de2e00dea35d0d3c2c4cc2d08c8dce3d4409ef19
[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.shutdown();
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     log.debug("Stopping pickManager");
240     haltPickmanager();
241     log.debug("Stopping EventGenerator..");
242     evgen.stopWatching();
243     SimpleClient.log.debug("EventGenerator halted.");
244     log.debug("Deregistering Client");
245     _session.removeClient(this);
246     log.debug("finalization Complete.");
247   }
248   
249   /*
250    * (non-Javadoc)
251    * 
252    * @see uk.ac.vamsas.client.IClient#getClientDocument()
253    */
254   public IClientDocument getClientDocument() throws IOException {
255     log.debug("getClientDocument");
256     if (cdocument!=null) {
257       // cdocument is non-nill if the ClientDocument.finalise() method hasn't been called.
258       return cdocument;
259     }
260     evgen.disableDocumentWatch();
261     VamsasArchive va = null;
262     try {
263       // LATER: bail out if it takes too long to get the lock ?
264       va = _session.getVamsasDocument();
265     }
266     catch (IOException e) {
267       throw new IOException("Failed to get lock on session document");
268     }
269     VamsasDocument doc=null;
270     IdFactory vorba = null;
271     // TODO: LATER: reduce size of vorba ids generated from these parameters to IdFactory (mainly sessionHandle rationalization ?)
272     try {
273       va.setVorba(vorba=makeVorbaIdFactory());
274       // if session currently holds data - read it in - or get a dummy
275       log.debug("Accessing document");
276       doc = 
277         va.getVamsasDocument(getProvenanceUser(),
278             "created new session document.", null);
279       if (doc!=null)
280         log.debug("Successfully retrieved document.");
281       else
282         log.error("Unexpectedly retrieved null document!");
283     }
284     catch (Exception e) {
285       log.error("Failed to get session document for session directory '"+_session.sessionDir+"'", e);
286       throw new IOException("Failed to get session document for session directory '"+_session.sessionDir+"'");
287     }
288     // Construct the IClientDocument instance
289     
290     cdocument = new ClientDocument(doc, va, vorba, this);
291     return cdocument;
292   }
293   
294   /*
295    * (non-Javadoc)
296    * @throws Errors for invalid newdoc parameter
297    * @see uk.ac.vamsas.client.IClient#updateDocument(uk.ac.vamsas.client.IClientDocument)
298    */
299   public void updateDocument(IClientDocument newdoc) {
300     log.debug("updateDocument:");
301     // Check validity of simpleclient instance and that it holds a lock on the session's document
302     if (!(newdoc instanceof ClientDocument)) {
303       throw new Error("Invalid IClientDocument passsed to SimpleClient.");
304     }
305     if (cdocument==null)
306       throw new Error("Client Error - updateDocument() called before getClientDocument() on this SimpleClient instance.");
307     if (newdoc!=cdocument)
308       throw new Error("Client Error - SimpleClient.updateDocument() can only take the IClientDocument instance returned from SimpleClient.getClientDocument()");
309
310     if (evgen.isDocumentWatchEnabled())
311       throw new Error("Client Error - or Library Bug : Document watcher still enabled whilst ClientDocument instance exists.");
312     
313     if (!cdocument.isModified()) {
314       // client document is silently got rid of, with no session update events.
315       if (log.isDebugEnabled())
316         log.debug("updateDocument for "+session.getSessionUrn()+" with unmodified IClientDocument (skipping the write)");
317     } else {
318       try {
319         if (!cdocument.updateSessionDocument()) {
320           log.warn("Session document did not update properly for session directory "+_session.sessionDir);
321           // cdocument.archive.cancelArchive(); // LATER: could be done - would need to prevent updateSessionDocument closing the iohandler.
322           _session.slog.warn("Session Document updated but may not be valid (false return from org.vamsas.simpleclient.ClientDocument.updateSessionDocument()");
323         }
324         log.debug("Document update successful.");
325         _session.setUnsavedFlag();
326       }
327       catch (IOException e) {
328         log.warn("IO Problems when updating document!",e);
329         _session.slog.error("IO problems when attempting to update document.");
330       }
331     }
332     // garbage collect the ClientDocument instance.
333     try {
334       log.debug("Finalizing ClientDocument instance.");
335       cdocument.finalize();
336     } catch (Throwable e) {
337       log.error("Exception when trying to garbage collect ClientDocument for "+session.getSessionUrn(), e);
338     }
339     cdocument = null; // this is probably done by finalize
340
341     try {
342       _session.unlockVamsasDocument();
343       evgen.enableDocumentWatch();
344     } catch (IOException e) {
345       log.warn("IO Problems when releasing lock on session document!",e);
346       _session.slog.error("IO problems when attempting to release lock on session document.");
347     }
348   }
349   
350   /*
351    * (non-Javadoc)
352    * 
353    * @see uk.ac.vamsas.client.IClient#storeDocument(java.io.File)
354    */
355   public void storeDocument(File location) {
356     if (location==null)
357       throw new Error("Vamsas Client API Usage Error: storeDocument called with null location.");
358     log.debug("StoreDocument to "+location);
359     // write storeDocument file to inform other clients that they should raise
360     Lock vamlock = evgen.want_to_store();
361     // Events.DOCUMENT_FINALIZEAPPDATA
362     try {
363       _session.writeVamsasDocument(location, vamlock);
364       _session.clearUnsavedFlag();
365     } catch (Exception e) {
366       log.warn("Exception whilst trying to store document in "+location,e);
367     }
368     
369     vamlock.release();
370   }
371   
372   /*
373    * (non-Javadoc)
374    * 
375    * @see uk.ac.vamsas.client.IClient#addVorbaEventHandler(java.lang.String,
376    *      java.beans.PropertyChangeListener)
377    */
378   public void addVorbaEventHandler(String EventChain, PropertyChangeListener evt) {
379     if (handlers.containsKey(EventChain)) {
380       log.debug("Adding new handler for "+EventChain);
381       Object handler;
382       ((PropertyChangeSupport) (handler = handlers.get(EventChain)))
383       .addPropertyChangeListener(evt);
384       listeners.add(handler);
385       listeners.add((Object) evt);
386     }
387   }
388   
389   /* (non-Javadoc)
390    * @see uk.ac.vamsas.client.IClient#pollUpdate()
391    */
392   public void pollUpdate() {
393     log.debug("pollUpdate");
394     if (evgen==null) {
395       log.warn("pollUpdate called on incomplete SimpleClient object.");
396       return;
397     }
398     
399     if (!evgen.isWatcherAlive()) {
400       log.warn("pollUpdate called before joinSession() - trying to do this.");
401       try {
402         joinSession();
403       } catch (Exception e) {
404         log.error("Unexpected exception on default call to joinSession",e);
405       }
406     }
407     
408     //TODO ensure event generator robustly handles these interrupts.
409     log.debug("interrrupting event generator.");
410     evgen.interruptWatching();
411     log.debug("interrrupted event generator.");
412   }
413   
414   /* (non-Javadoc)
415    * @see uk.ac.vamsas.client.IClient#joinSession()
416    */
417   public void joinSession() throws Exception {
418     log.debug("Joining Session.");
419     // start the EventGenerator thread.
420     if (evgen==null) {
421       log.warn("joinSession called on incomplete SimpleClient object.");
422       return;
423     }
424     if (evgen.isWatcherAlive())
425       throw new Error("Join session called twice for the same SimpleClient (IClient instance).");
426     evgen.startWatching();
427     if (evgen.isWatcherAlive())
428       log.debug("Started EventGenerator thread.");
429     else {
430       log.warn("Failed to start EventGenerator thread.");
431       throw new Exception("Failed to start event generator thread - client cannot be instantiated.");
432     }
433     if (evgen.countHandlersFor(Events.DOCUMENT_CREATE)>0) {
434       //TODO: LATER: is this application connecting to a newly created session document ?
435       //evgen.raise(Events.DOCUMENT_CREATE);
436     }
437     
438   }
439   
440   
441   
442   /* (non-Javadoc)
443    * @see uk.ac.vamsas.client.IClient#importDocument(java.io.File)
444    */
445   public void importDocument(File location) {
446     // TODO LATER: implement SimpleClient.importDocument()
447     log.error("importDocument is not yet implemented for a SimpleClient Session.");
448   }
449
450   public IObjectUpdate getUpdateHandler(Class rootObject) {
451     // TODO Auto-generated method stub
452     return null;
453   }
454
455   public IObjectUpdate[] getUpdateHandlers() {
456     // TODO Auto-generated method stub
457     return null;
458   }
459
460   public void removeUpdateHandler(Class rootObject) {
461     // TODO Auto-generated method stub
462     
463   }
464
465   public void setUpdateHandler(IObjectUpdate handler) {
466     // TODO Auto-generated method stub
467     
468   }
469
470   /**
471    * retrieves the current VamsasSession to which belong the client
472    * @return the _session
473    */
474   protected VamsasSession get_session() {
475     return this._session;
476   }
477   SimplePickManager pickmanager=null;
478   /* (non-Javadoc)
479    * @see uk.ac.vamsas.client.IClient#getPickManager()
480    */
481   public IPickManager getPickManager() {
482     createPickManager();
483     return pickmanager;
484   }
485
486   private void createPickManager() {
487     if (pickmanager==null){
488       // TODO: Construct PickManager for session using details from sessionURN!
489       log.debug("Creating PickManager (not from sessionURN yet)");
490       pickmanager = new SimplePickManager(new uk.ac.vamsas.client.picking.SocketManager());
491     }
492   }
493 }