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