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