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