moved methods for opening a stored session document in a new vamsas session into...
[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.io.RandomAccessFile;
14 import java.nio.channels.FileChannel;
15
16 import java.nio.channels.OverlappingFileLockException;
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.InvalidSessionDocumentException;
29 import uk.ac.vamsas.client.InvalidSessionUrnException;
30 import uk.ac.vamsas.client.SessionHandle;
31 import uk.ac.vamsas.client.UserHandle;
32 import uk.ac.vamsas.client.picking.IPickManager;
33 import uk.ac.vamsas.objects.core.Entry;
34 import uk.ac.vamsas.objects.core.VamsasDocument;
35 import uk.ac.vamsas.objects.utils.ProvenanceStuff;
36
37 /**
38  * @author jimp
39  */
40 public class SimpleClient implements IClient {
41   
42   private static Log log = LogFactory.getLog(SimpleClient.class);
43   
44   protected UserHandle user = null;
45   
46   protected SessionUrn session = null;
47   protected VamsasSession _session;
48   protected ClientHandle client = null;
49   protected EventGeneratorThread evgen = null;
50   protected ClientDocument cdocument = null;
51   
52   
53   
54   
55   private Lock activeClientFilelock = null;
56   private  File clientlockFile = null;
57   
58   /**
59    * object hash table that persists in each client holding vorbaIds and hash values after a document write
60    */
61   protected Hashtable extantobjects=null;
62   /**
63    * construct a transient IdFactory instance - this should last only as long as the 
64    * SimpleClient object holds the lock on the vamsas document being created/manipulated.
65    * @return
66    */
67   private IdFactory makeVorbaIdFactory() {
68     if (extantobjects==null)
69       extantobjects=new Hashtable();
70     return new IdFactory(getSessionHandle(), client, user, extantobjects);
71   }
72   
73   /**
74    * construct SimpleClient for user, client and VamsasSession directory
75    * use the SimpleClientFactory rather than this constructor directly. 
76    * @param user
77    * @param client
78    * @param sess
79    */
80   protected SimpleClient(UserHandle user, ClientHandle client, VamsasSession sess) throws InvalidSessionUrnException {
81     // TODO: validate user/client/session
82     _session = sess;
83     this.user = user;
84     this.client = client;
85     //try {
86     log.debug("Creating new session for "+_session);
87       session = new SessionUrn(_session);
88       log.debug("Creating new Event Generator");
89       evgen = new EventGeneratorThread(_session, this, handlers);
90       /*} catch (MalformedURLException e) {
91       log.error("Couldn't form a valid SessionUrn object!",e);
92       throw new InvalidSessionUrnException(_session.toString());
93     }*/
94       log.debug("SimpleClient constructed for session "+session.getSessionUrn());
95       
96   }
97   /**
98    * construct new SimpleClientsession by importing objects from an existing vamsas document
99    * @param user
100    * @param client
101    * @param sess
102    * @param importingArchive
103    * @throws Exception IOExceptions for Session IO problems, and general Exception if importing document is invalid.
104   protected SimpleClient(UserHandle user, ClientHandle client, VamsasSession sess, File importingArchive) throws Exception {
105     this(user, client, sess);
106     if (log.isDebugEnabled())
107     {
108       log.debug("Attempting to overwrite session document with file: "+importingArchive);
109     }
110   // TODO: write provenance entry for new session indicating the import.
111     
112   }
113    */
114   
115   /*
116    * (non-Javadoc)
117    * LATER: check that build substitution variables are correct
118    * @see uk.ac.vamsas.client.IClient#getAbout()
119    */
120   public String getAbout() {
121     return new String("VORBA SimpleClient version $version$ build $build$");
122   }
123   
124   /*
125    * (non-Javadoc)
126    * 
127    * @see uk.ac.vamsas.client.IClient#getSessionUrn()
128    */
129   public String getSessionUrn() {
130     return session.getSessionUrn();
131   }
132   
133   /*
134    * (non-Javadoc)
135    * 
136    * @see uk.ac.vamsas.client.IClient#getSessionHandle()
137    */
138   public SessionHandle getSessionHandle() {
139     // TODO: eliminate SessionHandle ? need to refactor interfaces.
140     SessionHandle sh = new SessionHandle(session.getSessionUrn());
141     return sh;
142   }
143   
144   /*
145    * (non-Javadoc)
146    * 
147    * @see uk.ac.vamsas.client.IClient#getClientHandle()
148    */
149   public ClientHandle getClientHandle() {
150     return client;
151   }
152   
153   /*
154    * (non-Javadoc)
155    * 
156    * @see uk.ac.vamsas.client.IClient#getUserHandle()
157    */
158   public UserHandle getUserHandle() {
159     return user;
160   }
161   /**
162    * 
163    * @return user field for a provenance entry
164    */
165   protected String getProvenanceUser() {
166     return new String(user.getFullName());
167   }
168   /**
169    * construct a provenance entry for this client with the specified action string.
170    * @param action
171    * @return properly completed provenance entry
172    */
173   protected Entry getProvenanceEntry(String action) {
174     Entry prov = ProvenanceStuff.newProvenanceEntry(client.getClientUrn(), getProvenanceUser(), action);
175     return prov;
176   }
177   private Hashtable handlers = initHandlers();
178   
179   private Vector listeners = new Vector();
180   
181   /**
182    * make all the PropertyChangeSupport objects for the
183    * events described in uk.ac.vamsas.client.Event
184    * @return
185    */
186   private static Hashtable initHandlers() {
187     Hashtable events = new Hashtable();
188     java.util.Iterator evt = Events.EventList.iterator();
189     while (evt.hasNext()) {
190       Object ths = evt.next();
191       events.put(ths, (Object) new PropertyChangeSupport(ths));
192     }
193     return events;
194   }
195   
196   /*
197    * (non-Javadoc)
198    * 
199    * @see uk.ac.vamsas.client.IClient#addDocumentUpdateHandler(java.util.EventListener)
200    */
201   public void addDocumentUpdateHandler(PropertyChangeListener evt) {
202     this.addVorbaEventHandler(Events.DOCUMENT_UPDATE, evt);
203   }
204   boolean finalized=false;
205   private void haltPickmanager() {
206     if (pickmanager!=null) {
207       final SimpleClient dying=this; 
208       new Thread() {
209         public void run() {
210           SimpleClient.log.debug("Stopping pickManager..");
211           dying.pickmanager.shutdown();
212           SimpleClient.log.debug("pickManager halted.");
213         }
214       }.start();
215     }
216   }
217   /*
218    * (non-Javadoc)
219    * 
220    * @see uk.ac.vamsas.client.IClient#finalizeClient()
221    */
222   public void finalizeClient() {
223     if (finalized)
224       throw new Error("VAMSAS Client Implementation Error: Finalized called twice for same client instance.");
225
226     // mark this instance as finalized
227     finalized=true;
228     
229     // TODO: determine if this is last client in session
230     
231     // TODO: raise events like : ((lst_client && document.request.to.close), (client_finalization), (
232     evgen._raise(Events.CLIENT_FINALIZATION, null, this,null);
233     // if (handlers.containsKey(Events.))
234     // if (handlers.containsKey(Events.CLIENT_FINALIZATION))
235     // deregister listeners.
236     log.debug("Stopping pickManager");
237     haltPickmanager();
238     
239     log.debug("Deregistering Client");
240     _session.removeClient(this);
241     //log.debug("Stopping EventGenerator..");
242     //evgen.stopWatching();
243     this.cdocument = null;
244     SimpleClient.log.debug("EventGenerator halted.");
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("Probable Client Error (did you remember to call SimpleClient.updateDocument(clientdoc) at the end of the document update handler?) - 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       writeSessionDocument();
318     }
319     tidyAwaySessionDocumentState(); 
320   }
321   /**
322    * garbage collect the ClientDocument instance and re-enable watchers.
323    */
324   protected void tidyAwaySessionDocumentState() {
325     try {
326       log.debug("Finalizing ClientDocument instance.");
327       cdocument.finalize();
328     } catch (Throwable e) {
329       log.error("Exception when trying to garbage collect ClientDocument for "+session.getSessionUrn(), e);
330     }
331     cdocument = null; // this is probably done by finalize
332
333     try {
334       _session.unlockVamsasDocument();
335       evgen.enableDocumentWatch();
336     } catch (IOException e) {
337       log.warn("IO Problems when releasing lock on session document!",e);
338       _session.slog.error("IO problems when attempting to release lock on session document.");
339     }
340   }
341
342   /**
343    * write the cdocument instance to the session for real.
344    */
345   private void writeSessionDocument() {
346     try {
347       boolean updated=cdocument.updateSessionDocument();
348       if (!updated) {
349         log.warn("Session document did not update properly for session directory "+_session.sessionDir);
350         // cdocument.archive.cancelArchive(); // LATER: could be done - would need to prevent updateSessionDocument closing the iohandler.
351         _session.slog.warn("Session Document updated but may not be valid (false return from org.vamsas.simpleclient.ClientDocument.updateSessionDocument()");
352       } else {
353         log.debug("Document update successful.");
354       }
355       _session.setUnsavedFlag();
356     }
357     catch (IOException e) {
358       log.warn("IO Problems when updating document!",e);
359       _session.slog.error("IO problems when attempting to update document.");
360     }
361   }
362
363   /*
364    * (non-Javadoc)
365    * 
366    * @see uk.ac.vamsas.client.IClient#storeDocument(java.io.File)
367    */
368   public void storeDocument(File location) {
369     if (location==null)
370       throw new Error("Vamsas Client API Usage Error: storeDocument called with null location.");
371     log.debug("StoreDocument to "+location);
372     // write storeDocument file to inform other clients that they should raise
373     Lock vamlock = evgen.want_to_store();
374     // Events.DOCUMENT_FINALIZEAPPDATA
375     try {
376       _session.writeVamsasDocument(location, vamlock);
377        _session.clearUnsavedFlag();
378     } catch (Exception e) {
379       log.warn("Exception whilst trying to store document in "+location,e);
380     }
381     vamlock.release();
382   }
383   
384   /*
385    * (non-Javadoc)
386    * 
387    * @see uk.ac.vamsas.client.IClient#addVorbaEventHandler(java.lang.String,
388    *      java.beans.PropertyChangeListener)
389    */
390   public void addVorbaEventHandler(String EventChain, PropertyChangeListener evt) {
391     if (handlers.containsKey(EventChain)) {
392       log.debug("Adding new handler for "+EventChain);
393       Object handler;
394       ((PropertyChangeSupport) (handler = handlers.get(EventChain)))
395       .addPropertyChangeListener(evt);
396       listeners.add(handler);
397       listeners.add((Object) evt);
398     }
399   }
400   
401   /* (non-Javadoc)
402    * @see uk.ac.vamsas.client.IClient#pollUpdate()
403    */
404   public void pollUpdate() {
405     log.debug("pollUpdate");
406     if (evgen==null) {
407       log.warn("pollUpdate called on incomplete SimpleClient object.");
408       return;
409     }
410     
411     if (!evgen.isWatcherAlive()) {
412       log.warn("pollUpdate called before joinSession() - trying to do this.");
413       try {
414         joinSession();
415       } catch (Exception e) {
416         log.error("Unexpected exception on default call to joinSession",e);
417       }
418     }
419     
420     //TODO ensure event generator robustly handles these interrupts.
421     log.debug("interrrupting event generator.");
422     evgen.interruptWatching();
423     log.debug("interrrupted event generator.");
424   }
425   
426   /* (non-Javadoc)
427    * @see uk.ac.vamsas.client.IClient#joinSession()
428    */
429   public void joinSession() throws Exception {
430     log.debug("Joining Session.");
431     // start the EventGenerator thread.
432     if (evgen==null) {
433       log.warn("joinSession called on incomplete SimpleClient object.");
434       return;
435     }
436     if (evgen.isWatcherAlive())
437       throw new Error("Join session called twice for the same SimpleClient (IClient instance).");
438     evgen.startWatching();
439     if (evgen.isWatcherAlive())
440       log.debug("Started EventGenerator thread.");
441     else {
442       log.warn("Failed to start EventGenerator thread.");
443       throw new Exception("Failed to start event generator thread - client cannot be instantiated.");
444     }
445     if (evgen.countHandlersFor(Events.DOCUMENT_CREATE)>0) {
446       //TODO: LATER: is this application connecting to a newly created session document ?
447       //evgen.raise(Events.DOCUMENT_CREATE);
448     }
449     
450   }
451   
452   
453   
454   /* (non-Javadoc)
455    * @see uk.ac.vamsas.client.IClient#importDocument(java.io.File)
456    */
457   public void importDocument(File location) {
458     // TODO LATER: implement SimpleClient.importDocument()
459     // if (numberOfClients<1 or document file is empty/new, then can import directly.)
460     // more complex if data is already present in document. Could have a 'clearSession' method, too, or dump and overwrite instead.
461     log.error("importDocument is not yet implemented for a SimpleClient Session.");
462     
463     /*try {
464       this._session.setVamsasDocument(location);
465     } catch (IOException e) {
466       log.error("importDocument failed.");
467     }*/
468   }
469
470   public IObjectUpdate getUpdateHandler(Class rootObject) {
471     // TODO Auto-generated method stub
472     return null;
473   }
474
475   public IObjectUpdate[] getUpdateHandlers() {
476     // TODO Auto-generated method stub
477     return null;
478   }
479
480   public void removeUpdateHandler(Class rootObject) {
481     // TODO Auto-generated method stub
482     
483   }
484
485   public void setUpdateHandler(IObjectUpdate handler) {
486     // TODO Auto-generated method stub
487     
488   }
489
490   /**
491    * retrieves the current VamsasSession to which belong the client
492    * @return the _session
493    */
494   protected VamsasSession get_session() {
495     return this._session;
496   }
497   SimplePickManager pickmanager=null;
498   /* (non-Javadoc)
499    * @see uk.ac.vamsas.client.IClient#getPickManager()
500    */
501   public IPickManager getPickManager() {
502     createPickManager();
503     return pickmanager;
504   }
505
506   private void createPickManager() {
507     if (pickmanager==null){
508       // TODO: Construct PickManager for session using details from sessionURN!
509       log.debug("Creating PickManager (not from sessionURN yet)");
510       pickmanager = new SimplePickManager(new uk.ac.vamsas.client.picking.SocketManager());
511     }
512   }
513   
514   
515   protected void releaseActiveClientFile() throws IOException
516   {
517    
518     log.debug("Releasing active client locks");
519     if( activeClientFilelock != null)
520     {// Release the lock
521       log.debug("Releasing lock on active client lock file");
522       activeClientFilelock.release();
523       log.debug("ReleaseActiveClientFile called when client has no lock on its clientLockFile");
524       activeClientFilelock = null;
525     } else {
526       log.debug("ReleaseActiveClientFile called when client has no lock on its clientLockFile");
527     }
528     if (this.clientlockFile != null)
529     {
530       log.debug("trying to delete active client lock file");
531       if (this.clientlockFile.exists())
532       {
533         this.clientlockFile.delete();
534         log.debug("deleted active client lock file");
535       }
536     } else {
537       log.debug("ReleaseActiveClientFile called when client has no clientLockFile");
538     }
539   }
540   
541   protected void  createActiveClientFile() throws IOException
542   {
543     if(this.clientlockFile != null )return; 
544    log.debug("createActiveClientFile");
545     //create, if need,  subdirectory to contain client files
546    File clientlockFileDir = new File ( this.get_session().sessionDir, this.get_session().clientFileDirectory);
547     if( !clientlockFileDir.exists())
548       {//the directory does not exist, create it
549         if (! clientlockFileDir.mkdirs())
550         {
551           throw new IOException("Failed to create sub directory to session directory  for client lock files'"+clientlockFileDir.getAbsolutePath()+"'");
552         }
553       }
554     else
555     {
556       if (!(clientlockFileDir.isDirectory() && clientlockFileDir.canWrite()))
557       {
558         throw new IOException("Directory  for client lock files is not a directory or is not accessibl: '"+clientlockFileDir.getAbsolutePath()+"'");
559        }
560     }
561     this.clientlockFile = new File (clientlockFileDir, this.getClientHandle().getClientUrn().replaceAll("[:;/\\\\]+",""));
562     
563     log.debug("Creating active client lock file "+ this.clientlockFile.getAbsolutePath());
564     Lock clientLock = uk.ac.vamsas.client.simpleclient.LockFactory.getLock(clientlockFile, false);
565     if (clientLock==null || !clientLock.isLocked())
566     {
567       log.fatal("IMPLEMENTATION ERROR: Couldn't get a lock for the client lock file "+clientlockFile);
568     }
569     activeClientFilelock = clientLock;
570   }
571   /**
572    * @return the clientlockFile
573    */
574   protected File getClientlockFile() {
575     return clientlockFile;
576   }
577   /**
578    * 
579    * @return the lock for the client in the session
580    */
581   protected Lock getClientLock() {
582     return activeClientFilelock;
583   }
584
585 }