677094ce3faa22770f72f889cea379286765ec33
[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         _session.setUnsavedFlag();
325       }
326       catch (IOException e) {
327         log.warn("IO Problems when updating document!",e);
328         _session.slog.error("IO problems when attempting to update document.");
329       }
330     }
331     // garbage collect the ClientDocument instance.
332     try {
333       log.debug("Finalizing ClientDocument instance.");
334       cdocument.finalize();
335     } catch (Throwable e) {
336       log.error("Exception when trying to garbage collect ClientDocument for "+session.getSessionUrn(), e);
337     }
338     cdocument = null; // this is probably done by finalize
339
340     try {
341       _session.unlockVamsasDocument();
342       evgen.enableDocumentWatch();
343     } catch (IOException e) {
344       log.warn("IO Problems when releasing lock on session document!",e);
345       _session.slog.error("IO problems when attempting to release lock on session document.");
346     }
347   }
348   
349   /*
350    * (non-Javadoc)
351    * 
352    * @see uk.ac.vamsas.client.IClient#storeDocument(java.io.File)
353    */
354   public void storeDocument(File location) {
355     if (location==null)
356       throw new Error("Vamsas Client API Usage Error: storeDocument called with null location.");
357     log.debug("StoreDocument to "+location);
358     // write storeDocument file to inform other clients that they should raise
359     Lock vamlock = evgen.want_to_store();
360     // Events.DOCUMENT_FINALIZEAPPDATA
361     try {
362       _session.writeVamsasDocument(location, vamlock);
363       _session.clearUnsavedFlag();
364     } catch (Exception e) {
365       log.warn("Exception whilst trying to store document in "+location,e);
366     }
367     
368     vamlock.release();
369   }
370   
371   /*
372    * (non-Javadoc)
373    * 
374    * @see uk.ac.vamsas.client.IClient#addVorbaEventHandler(java.lang.String,
375    *      java.beans.PropertyChangeListener)
376    */
377   public void addVorbaEventHandler(String EventChain, PropertyChangeListener evt) {
378     if (handlers.containsKey(EventChain)) {
379       log.debug("Adding new handler for "+EventChain);
380       Object handler;
381       ((PropertyChangeSupport) (handler = handlers.get(EventChain)))
382       .addPropertyChangeListener(evt);
383       listeners.add(handler);
384       listeners.add((Object) evt);
385     }
386   }
387   
388   /* (non-Javadoc)
389    * @see uk.ac.vamsas.client.IClient#pollUpdate()
390    */
391   public void pollUpdate() {
392     log.debug("pollUpdate");
393     if (evgen==null) {
394       log.warn("pollUpdate called on incomplete SimpleClient object.");
395       return;
396     }
397     
398     if (!evgen.isWatcherAlive()) {
399       log.warn("pollUpdate called before joinSession() - trying to do this.");
400       try {
401         joinSession();
402       } catch (Exception e) {
403         log.error("Unexpected exception on default call to joinSession",e);
404       }
405     }
406     
407     //TODO ensure event generator robustly handles these interrupts.
408     log.debug("interrrupting event generator.");
409     evgen.interruptWatching();
410     log.debug("interrrupted event generator.");
411   }
412   
413   /* (non-Javadoc)
414    * @see uk.ac.vamsas.client.IClient#joinSession()
415    */
416   public void joinSession() throws Exception {
417     log.debug("Joining Session.");
418     // start the EventGenerator thread.
419     if (evgen==null) {
420       log.warn("joinSession called on incomplete SimpleClient object.");
421       return;
422     }
423     if (evgen.isWatcherAlive())
424       throw new Error("Join session called twice for the same SimpleClient (IClient instance).");
425     evgen.startWatching();
426     if (evgen.isWatcherAlive())
427       log.debug("Started EventGenerator thread.");
428     else {
429       log.warn("Failed to start EventGenerator thread.");
430       throw new Exception("Failed to start event generator thread - client cannot be instantiated.");
431     }
432     if (evgen.countHandlersFor(Events.DOCUMENT_CREATE)>0) {
433       //TODO: LATER: is this application connecting to a newly created session document ?
434       //evgen.raise(Events.DOCUMENT_CREATE);
435     }
436     
437   }
438   
439   
440   
441   /* (non-Javadoc)
442    * @see uk.ac.vamsas.client.IClient#importDocument(java.io.File)
443    */
444   public void importDocument(File location) {
445     // TODO LATER: implement SimpleClient.importDocument()
446     log.error("importDocument is not yet implemented for a SimpleClient Session.");
447   }
448
449   public IObjectUpdate getUpdateHandler(Class rootObject) {
450     // TODO Auto-generated method stub
451     return null;
452   }
453
454   public IObjectUpdate[] getUpdateHandlers() {
455     // TODO Auto-generated method stub
456     return null;
457   }
458
459   public void removeUpdateHandler(Class rootObject) {
460     // TODO Auto-generated method stub
461     
462   }
463
464   public void setUpdateHandler(IObjectUpdate handler) {
465     // TODO Auto-generated method stub
466     
467   }
468
469   /**
470    * retrieves the current VamsasSession to which belong the client
471    * @return the _session
472    */
473   protected VamsasSession get_session() {
474     return this._session;
475   }
476   SimplePickManager pickmanager=null;
477   /* (non-Javadoc)
478    * @see uk.ac.vamsas.client.IClient#getPickManager()
479    */
480   public IPickManager getPickManager() {
481     createPickManager();
482     return pickmanager;
483   }
484
485   private void createPickManager() {
486     if (pickmanager==null){
487       // TODO: Construct PickManager for session using details from sessionURN!
488       log.debug("Creating PickManager (not from sessionURN yet)");
489       pickmanager = new SimplePickManager(new uk.ac.vamsas.client.picking.SocketManager());
490     }
491   }
492 }