introducing Lock File based locking (for portability) - part implemented.
[vamsas.git] / src / org / 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 org.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 import org.vamsas.client.ClientHandle;
23 import org.vamsas.client.Events;
24 import org.vamsas.client.IClient;
25 import org.vamsas.client.IClientDocument;
26 import org.vamsas.client.InvalidSessionUrnException;
27 import org.vamsas.client.SessionHandle;
28 import org.vamsas.client.UserHandle;
29 import org.vamsas.objects.core.ApplicationData;
30 import org.vamsas.objects.core.Entry;
31 import org.vamsas.objects.core.LockFile;
32 import org.vamsas.objects.core.VamsasDocument;
33 import org.vamsas.objects.utils.AppDataReference;
34 import org.vamsas.objects.utils.ProvenanceStuff;
35 import org.vamsas.objects.utils.document.VersionEntries;
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    * construct a transient IdFactory instance - this should last only as long as the 
53    * SimpleClient object holds the lock on the vamsas document being created/manipulated.
54    * @return
55    */
56   private IdFactory makeVorbaIdFactory() {
57     return new IdFactory(getSessionHandle(), client, user);
58   }
59   
60   /**
61    * construct SimpleClient for user, client and VamsasSession directory
62    * use the SimpleClientFactory rather than this constructor directly. 
63    * @param user
64    * @param client
65    * @param sess
66    */
67   protected SimpleClient(UserHandle user, ClientHandle client, VamsasSession sess) throws InvalidSessionUrnException {
68     // TODO: validate user/client/session
69     _session = sess;
70     this.user = user;
71     this.client = client;
72     try {
73       session = new SessionUrn(_session);
74     } catch (MalformedURLException e) {
75       log.error("Couldn't form a valid SessionUrn object!",e);
76       throw new InvalidSessionUrnException(_session.toString());
77     }
78   }
79   /**
80    * construct new session by importing objects from an existing vamsas document
81    * @param user
82    * @param client
83    * @param sess
84    * @param importingArchive
85    * @throws Exception IOExceptions for Session IO problems, and general Exception if importing document is invalid.
86    */
87   protected SimpleClient(UserHandle user, ClientHandle client, VamsasSession sess, File importingArchive) throws Exception {
88     this(user, client, sess);
89     VamsasArchive sessdoc = _session.getVamsasDocument();
90     try {
91       VamsasArchiveReader odoc = new VamsasArchiveReader(importingArchive);
92       SimpleDocument sdoc = new SimpleDocument(makeVorbaIdFactory());
93       VamsasDocument doc = sdoc.getVamsasDocument(odoc);
94       sessdoc.putVamsasDocument(doc, sdoc.vorba);
95       sessdoc.closeArchive();
96     } catch (Exception e) {
97       sessdoc.cancelArchive();
98       // write a dummy archive
99       _session.slog.info("Exception when importing document data from "+importingArchive);
100       throw new Exception("Failed to import data from "+importingArchive, e);
101     }
102   }
103   
104   /*
105    * (non-Javadoc)
106    * LATER: check that build substitution variables are correct
107    * @see org.vamsas.client.IClient#getAbout()
108    */
109   public String getAbout() {
110     return new String("VORBA SimpleClient version $version$ build $build$");
111   }
112   
113   /*
114    * (non-Javadoc)
115    * 
116    * @see org.vamsas.client.IClient#getSessionUrn()
117    */
118   public String getSessionUrn() {
119     return session.getSessionUrn();
120   }
121   
122   /*
123    * (non-Javadoc)
124    * 
125    * @see org.vamsas.client.IClient#getSessionHandle()
126    */
127   public SessionHandle getSessionHandle() {
128     // TODO: eliminate SessionHandle ? need to refactor interfaces.
129     SessionHandle sh = new SessionHandle(session.getSessionUrn());
130     return sh;
131   }
132   
133   /*
134    * (non-Javadoc)
135    * 
136    * @see org.vamsas.client.IClient#getClientHandle()
137    */
138   public ClientHandle getClientHandle() {
139     return client;
140   }
141   
142   /*
143    * (non-Javadoc)
144    * 
145    * @see org.vamsas.client.IClient#getUserHandle()
146    */
147   public UserHandle getUserHandle() {
148     return user;
149   }
150   /**
151    * 
152    * @return user field for a provenance entry
153    */
154   protected String getProvenanceUser() {
155     return new String(user.getFullName());
156   }
157   /**
158    * construct a provenance entry for this client with the specified action string.
159    * @param action
160    * @return properly completed provenance entry
161    */
162   protected Entry getProvenanceEntry(String action) {
163     Entry prov = ProvenanceStuff.newProvenanceEntry(client.getClientUrn(), getProvenanceUser(), action);
164     return prov;
165   }
166   private Hashtable handlers = initHandlers();
167   
168   private Vector listeners = new Vector();
169   
170   /**
171    * make all the PropertyChangeSupport objects for the
172    * events described in org.vamsas.client.Event
173    * @return
174    */
175   private static Hashtable initHandlers() {
176     Hashtable events = new Hashtable();
177     java.util.Iterator evt = Events.EventList.iterator();
178     while (evt.hasNext()) {
179       Object ths = evt.next();
180       events.put(ths, (Object) new PropertyChangeSupport(ths));
181     }
182     return events;
183   }
184   
185   /*
186    * (non-Javadoc)
187    * 
188    * @see org.vamsas.client.IClient#addDocumentUpdateHandler(java.util.EventListener)
189    */
190   public void addDocumentUpdateHandler(PropertyChangeListener evt) {
191     if (handlers.containsKey(Events.DOCUMENT_UPDATE)) {
192       Object handler;
193       ((PropertyChangeSupport) (handler = handlers.get(Events.DOCUMENT_UPDATE)))
194       .addPropertyChangeListener(evt);
195       listeners.add(handler);
196       listeners.add((Object) evt);
197     }
198   }
199   boolean finalized=false;
200   /*
201    * (non-Javadoc)
202    * 
203    * @see org.vamsas.client.IClient#finalizeClient()
204    */
205   public void finalizeClient() {
206     // TODO: determine if this is last client in session
207     // TODO: raise events like : ((lst_client && document.request.to.close), (client_finalization), (
208     
209     // if (handlers.containsKey(Events.))
210     // if (handlers.containsKey(Events.CLIENT_FINALIZATION))
211     // deregister listeners.
212     // mark this instance as finalized
213   }
214   
215   /*
216    * (non-Javadoc)
217    * 
218    * @see org.vamsas.client.IClient#getClientDocument()
219    */
220   public IClientDocument getClientDocument() throws IOException {
221     if (cdocument!=null) {
222       // cdocument is non-nill if the ClientDocument.finalise() method hasn't been called.
223       return cdocument;
224     }
225     VamsasArchive va = null;
226     try {
227       // LATER: bail out if it takes too long to get the lock ?
228       va = _session.getVamsasDocument();
229     }
230     catch (IOException e) {
231       throw new IOException("Failed to get lock on session document");
232     }
233     VamsasDocument doc=null;
234     IdFactory vorba = null;
235     // TODO: reduce size of vorba ids generated from these parameters to IdFactory (mainly sessionHandle rationalization ?)
236     try {
237       va.setVorba(vorba=makeVorbaIdFactory());
238       // if session currently holds data - read it in - or get a dummy
239       _session.slog.debug("Accessing document");
240       doc = 
241         va.getVamsasDocument(getProvenanceUser(),
242             "created new session document.", null);
243       if (doc!=null)
244         _session.slog.debug("Successfully retrieved document.");
245       else
246         log.error("Unexpectedly retrieved null document!");
247     }
248     catch (Exception e) {
249       log.error("Failed to get session document for session directory '"+_session.sessionDir+"'", e);
250       throw new IOException("Failed to get session document for session directory '"+_session.sessionDir+"'");
251     }
252     // Construct the IClientDocument instance
253     
254     ClientDocument cdoc = new ClientDocument(doc, va, vorba, this);
255     return cdoc;
256   }
257   
258   /*
259    * (non-Javadoc)
260    * @throws Errors for invalid newdoc parameter
261    * @see org.vamsas.client.IClient#updateDocument(org.vamsas.client.IClientDocument)
262    */
263   public void updateDocument(IClientDocument newdoc) {
264     if (!(newdoc instanceof ClientDocument)) {
265       throw new Error("Invalid IClientDocument instance for SimpleClient.");
266     }
267     if (cdocument==null)
268       throw new Error("Client Error - updateDocument() called before getClientDocument().");
269     if (newdoc!=cdocument)
270       throw new Error("Client Error - SimpleClient.updateDocument() can only take the IClientDocument instance returned from SimpleClient.getClientDocument()");
271     if (!cdocument.isModified()) {
272       if (log.isDebugEnabled())
273         log.debug("updateDocument for "+session.getSessionUrn()+" with unmodified IClientDocument.");
274     } else {
275       try {
276         if (!cdocument.updateSessionDocument()) {
277           log.warn("Session document did not update properly for session directory "+_session.sessionDir);
278           // cdocument.archive.cancelArchive(); // LATER: could be done - would need to prevent updateSessionDocument closing the archive.
279           _session.slog.warn("Session Document updated but may not be valid (false return from org.vamsas.simpleclient.ClientDocument.updateSessionDocument()");
280         }
281       }
282       catch (IOException e) {
283         log.warn("IO Problems when updating document!",e);
284         _session.slog.error("IO problems when attempting to update document.");
285       }
286     }
287     // garbage collect the ClientDocument instance.
288     try {
289       cdocument.finalize();
290
291     } catch (Throwable e) {
292       log.error("Exception when trying to garbage collect ClientDocument for "+session.getSessionUrn(), e);
293     }
294     cdocument = null; // this is probably done by finalize
295   }
296   
297   /*
298    * (non-Javadoc)
299    * 
300    * @see org.vamsas.client.IClient#storeDocument(java.io.File)
301    */
302   public void storeDocument(File location) {
303     
304     // write storeDocument file to inform other clients that they should raise
305     Lock vamlock = evgen.want_to_store();
306     // Events.DOCUMENT_FINALIZEAPPDATA
307     try {
308       _session.writeVamsasDocument(location, vamlock);
309       _session.clearUnsavedFlag();
310     } catch (Exception e) {
311       log.warn("Exception whilst trying to store document in "+location,e);
312     }
313     
314     vamlock.release();
315   }
316   
317   /*
318    * (non-Javadoc)
319    * 
320    * @see org.vamsas.client.IClient#addVorbaEventHandler(java.lang.String,
321    *      java.beans.PropertyChangeListener)
322    */
323   public void addVorbaEventHandler(String EventChain, PropertyChangeListener evt) {
324     if (handlers.containsKey(EventChain)) {
325       Object handler;
326       ((PropertyChangeSupport) (handler = handlers.get(EventChain)))
327       .addPropertyChangeListener(evt);
328       listeners.add(handler);
329       listeners.add((Object) evt);
330     }
331   }
332   
333   /* (non-Javadoc)
334    * @see org.vamsas.client.IClient#pollUpdate()
335    */
336   public void pollUpdate() {
337     
338     if (evgen==null) {
339       log.warn("pollUpdate called on incomplete SimpleClient object.");
340       return;
341     }
342     
343     if (!evgen.isAlive()) {
344       log.warn("pollUpdate called before joinSession() - trying to do this.");
345       try {
346         joinSession();
347       } catch (Exception e) {
348         log.error("Unexpected exception on default call to joinSession",e);
349       }
350     }
351     
352     //TODO ensure event generator robustly handles these interrupts.
353     log.debug("interrrupting event generator.");
354     evgen.interrupt();
355     log.debug("interrrupted event generator.");
356   }
357   
358   /* (non-Javadoc)
359    * @see org.vamsas.client.IClient#joinSession()
360    */
361   public void joinSession() throws Exception {
362     // start the EventGenerator thread.
363     if (evgen==null) {
364       log.warn("joinSession called on incomplete SimpleClient object.");
365       return;
366     }
367     if (evgen.isAlive())
368       throw new Error("Join session called twice for the same SimpleClient (IClient instance).");
369     evgen.start();
370     if (evgen.isAlive())
371       log.debug("Started EventGenerator thread.");
372     else {
373       log.warn("Failed to start EventGenerator thread.");
374       throw new Exception("Failed to start event generator thread - client cannot be instantiated.");
375     }
376     if (evgen.countHandlersFor(Events.DOCUMENT_CREATE)>0) {
377       //TODO: is this application connecting to a newly created session document ?
378       //evgen.raise(Events.DOCUMENT_CREATE);
379     }
380   }
381   
382   
383   
384   /* (non-Javadoc)
385    * @see org.vamsas.client.IClient#importDocument(java.io.File)
386    */
387   public void importDocument(File location) {
388     // TODO LATER: implement SimpleClient.importDocument()
389     log.error("importDocument is not yet implemented for a SimpleClient Session.");
390   }
391 }