SimpleClient implementation testable against uk.ac.vamsas.test.ExampleApplication
[vamsas.git] / src / uk / ac / vamsas / client / simpleclient / VamsasSession.java
1 package uk.ac.vamsas.client.simpleclient;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.PrintStream;
6 import java.io.PrintWriter;
7 import java.io.RandomAccessFile;
8 import java.io.Writer;
9 import java.util.Enumeration;
10
11 import org.apache.commons.logging.Log;
12 import org.apache.commons.logging.LogFactory;
13 import org.apache.log4j.Appender;
14 import org.apache.log4j.Logger;
15 import org.apache.log4j.FileAppender;
16 import org.apache.log4j.PatternLayout;
17
18 import uk.ac.vamsas.client.ClientHandle;
19 import uk.ac.vamsas.client.IClient;
20 import uk.ac.vamsas.client.UserHandle;
21 /**
22  * Does all the IO operations for a SimpleClient instance accessing 
23  * a SimpleClient vamsas session.
24  * 
25  * Basically, it defines the various standard names for the files 
26  * in the session directory (that maps to the sessionUrn), 
27  * provides constructors for the file handlers and watchers of 
28  * those file entities, and some higher level methods 
29  * to check and change the state flags for the session.
30  * 
31  * TODO: move the stuff below to the SimpleClientFactory documentation.
32  * much may not be valid now :
33  * Vamsas client is intialised with a path to create live session directories. 
34  * This path may contain a vamsas.properties file 
35  * that sets additional parameters (otherwise client 
36  * just uses the one on the classpath).
37  * 
38  * A vamsas session consists of :
39  *  SessionDir - translates to urn of a live session.
40  *  Contains: Vamsas Document (as a jar), Session client list file, 
41  *  both of which may be locked, and additional 
42  *  temporary versions of these files when write 
43  *  operations are taking place.
44  * 
45  * Zip file entries
46  *  - vamsasdocument.xml : core info
47  *  one or more:
48  *  - <applicationname>.version.sessionnumber.raw (string given in vamsasdocument.xml applicationData entry)
49  *  
50  * Lockfile
51  *  - filename given in the vamsasdocument.xml. Should be checked for validity by any client and rewritten if necessary. 
52  *    The lockfile can point to the jar itself.
53  * Mode of operation.
54  * Initially - documentHandler either:
55  *  - creates a zip for a new session for the client
56  *  - connect to an existing session zip 
57  *   1. reads session urn file
58  *   2. waits for lock
59  *   3. examines session - decide whether to create new application data slice or connect to one stored in session.
60  *   4. writes info into session file
61  *   5. releases lock and generates local client events.
62  *   6. Creates Watcher thread to generate events.
63  * 
64  * During the session
65  *  - Update watcher checks for file change - 
66  * 
67  * Procedures for file based session message exchange
68  *  - session document modification flag
69  *    
70  */
71
72 public class VamsasSession {
73   /**
74    * indicator file for informing other processes that 
75    * they should finalise their vamsas datasets for 
76    * storing into a vamsas archive.
77    */
78   public static final String CLOSEANDSAVE_FILE="stored.log";
79   /**
80    * session file storing the last_stored_stat data 
81    */
82   public static final String MODIFIEDDOC_FILE="modified";
83
84   
85   private SimpleSessionManager sessionManager = null;
86   
87   /**
88    * called to clear update flag after a successful offline storage event
89    */
90   protected void clearUnsavedFlag() {
91     SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
92     if (!laststored.clearFlag())
93       log.warn("Unsaved flag was not cleared for "+sessionDir);
94   }
95   /**
96    * called to indicate session document has been modified.
97    *
98    */
99   protected void setUnsavedFlag() {
100     SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
101     if (!laststored.setFlag())
102       log.warn("Couldn't set the Unsaved flag for "+sessionDir);
103   }
104   /**
105    * 
106    * @return true if session document has been modified since last offline storage event 
107    */
108   protected boolean getUnsavedFlag() {
109     SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
110     return laststored.checkFlag();
111   }
112   /**
113    * log file location
114    */
115   public static final String SESSION_LOG="Log.txt";
116   private static Log log = LogFactory.getLog(VamsasSession.class);
117   protected Logger slog = Logger.getLogger("uk.ac.vamsas.client.SessionLog");
118   /**
119    * setup the sessionLog using Log4j.
120    * @throws IOException
121    */
122   private void initLog() throws IOException {
123     // TODO: fix session event logging
124     // LATER: make dedicated appender format for session log.
125     /*Appender app = slog.getAppender("log4j.appender.SESSIONLOG");
126     // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
127     // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
128     for (Enumeration e = slog.getAllAppenders() ; e.hasMoreElements() ;) {
129       System.out.println(e.nextElement());
130
131     }*/
132   
133     if (slog!= null ) {
134       File sessionLogFile =  new File(this.sessionDir, SESSION_LOG);
135       slog.addAppender(new FileAppender(new PatternLayout("%-4r [%t] %-5p %c %x - %m%n"), sessionLogFile.getAbsolutePath(), true));
136     } else {
137       log.info("No appender for SessionLog");
138     }
139   }
140   
141   /**
142    * the sessionDir is given as the session location for new clients.
143    */
144   protected File sessionDir;
145   /**
146    * holds the list of attached clients
147    */
148   ClientsFile clist;
149   public static final String CLIENT_LIST="Clients.obj";
150   /**
151    * holds the data
152    */
153   VamsasFile vamArchive; 
154   public static final String VAMSAS_OBJ="VamDoc.jar";
155   
156   /**
157    * sets up the vamsas session files and watchers in sessionDir
158    * @param sessionDir1
159    */
160   protected VamsasSession(File sessionDir1) throws IOException {
161     if (sessionDir1==null)
162       throw new Error("Null directory for VamsasSession.");
163     if (sessionDir1.exists()) {
164       if (!sessionDir1.isDirectory() || !sessionDir1.canWrite() || !sessionDir1.canRead())
165         throw new IOException("Cannot access '"+sessionDir1+"' as a read/writable Directory.");
166       if (!checkSessionFiles(sessionDir1))
167       log.warn("checkSessionFiles() returned false. Possible client implementation error");
168       this.sessionDir = sessionDir1; 
169       initSessionObjects();
170       slog.debug("Initialising additional VamsasSession instance");
171       log.debug("Attached to VamsasSession in "+sessionDir1);
172       //} 
173     } else {
174       // start from scratch
175       if (!sessionDir1.mkdir())
176         throw new IOException("Failed to make VamsasSession directory in "+sessionDir1);
177       createSessionFiles();
178       initSessionObjects();
179       slog.debug("Session directory created.");
180       log.debug("Initialised VamsasSession in "+sessionDir1);
181     }
182   }
183   /**
184    * tests presence of existing sessionfiles files in dir
185    * @param dir
186    * @return
187    */
188   private boolean checkSessionFiles(File dir) throws IOException {
189     File c_file = new File(dir,CLIENT_LIST);
190     File v_doc = new File(dir,VAMSAS_OBJ);
191     if (c_file.exists() && v_doc.exists())
192       return true;
193     return false;
194   }
195   /**
196    * create new empty files in dir
197    *
198    */
199   private void createSessionFiles() throws IOException {
200     if (sessionDir==null)
201       throw new IOException("Invalid call to createSessionFiles() with null sessionDir");
202     File c_file = new File(sessionDir,CLIENT_LIST);
203     File v_doc = new File(sessionDir,VAMSAS_OBJ);
204     if (!c_file.exists() && c_file.createNewFile())
205       log.debug("Created new ClientFile "+c_file); // don't care if this works or not
206     if (!v_doc.exists() && v_doc.createNewFile())
207       log.debug("Created new Vamsas Session Document File "+v_doc); 
208   }
209   /**
210    * construct SessionFile objects and watchers for each
211    */
212   private void initSessionObjects() throws IOException {
213     createSessionFiles();
214     if (clist!=null || vamArchive!=null)
215       throw new IOException("initSessionObjects called for initialised VamsasSession object.");
216     clist = new ClientsFile(new File(sessionDir,CLIENT_LIST));
217     vamArchive = new VamsasFile(new File(sessionDir,VAMSAS_OBJ));
218     storedocfile=new ClientsFile(new File(sessionDir, CLOSEANDSAVE_FILE));
219     initLog();
220   }
221   /**
222    * make a new watcher object for the clientFile
223    * @return new ClientFile watcher instance
224    */
225   public FileWatcher getClientWatcher() {
226     return new FileWatcher(clist.sessionFile);
227   }
228   /**
229    * make a new watcher object for the vamsas Document
230    * @return new ClientFile watcher instance
231    */
232   public FileWatcher getDocWatcher() {
233     return new FileWatcher(vamArchive.sessionFile);
234   }
235   FileWatcher store_doc_file=null;
236   public ClientsFile storedocfile=null;
237   /**
238    * make a new watcher object for the messages file
239    * @return new watcher instance
240    */
241   public FileWatcher getStoreWatcher() {
242     return new FileWatcher(new File(sessionDir,CLOSEANDSAVE_FILE));
243
244   }
245   /**
246    * write to the StoreWatcher file to indicate that a storeDocumentRequest has been made.
247    * The local client's storeWatcher FileWatcher object is updated so the initial change is not registered.
248    * @param client
249    * @param user
250    * @return
251    */
252   public void addStoreDocumentRequest(ClientHandle client, UserHandle user) throws IOException {
253     // TODO: replace this with clientsFile mechanism
254     SessionFile sfw = new SessionFile(new File(sessionDir, CLOSEANDSAVE_FILE));
255     while (!sfw.lockFile())
256       log.debug("Trying to get lock for "+CLOSEANDSAVE_FILE);
257     RandomAccessFile sfwfile=sfw.fileLock.getRaFile();
258     sfwfile.setLength(0); // wipe out any old info.
259     // TODO: rationalise what gets written to this file (ie do we want other clients to read the id of the requestor?)
260     sfwfile.writeUTF(client.getClientUrn()+":"+user.getFullName()+"@"+user.getOrganization());
261     sfw.unlockFile();
262     if (store_doc_file!=null)
263       store_doc_file.setState();
264     slog.info("FinalizeAppData request from "+user.getFullName()+" using "+client.getClientUrn()+"");
265   }
266   /**
267    * create a new session with an existing vamsas Document - by copying it into the session.
268    * @param archive
269    */
270   public void setVamsasDocument(File archive) throws IOException {
271     log.debug("Transferring vamsas data from "+archive+" to session:"+vamArchive.sessionFile);
272     SessionFile xtantdoc = new SessionFile(archive);
273     vamArchive.updateFrom(null, xtantdoc);
274     // LATER: decide if session archive provenance should be updated to reflect access.
275     // TODO: soon! do a proper import objects from external file 
276     log.debug("Transfer complete.");
277   }
278   /**
279    * write session as a new vamsas Document (this will overwrite any existing file without warning)
280    * TODO: test
281    * TODO: verify that lock should be released for vamsas document.
282    * @param destarchive
283    */
284   protected void writeVamsasDocument(File destarchive, Lock extlock) throws IOException {
285     log.debug("Transferring vamsas data from "+vamArchive.sessionFile+" to session:"+destarchive);
286     SessionFile newdoc = new SessionFile(destarchive);
287     if (extlock==null && !vamArchive.lockFile())
288       while (!vamArchive.lockFile())
289         log.info("Trying to get lock for "+vamArchive.sessionFile);
290     // TODO: LATER: decide if a provenance entry should be written in the exported document recording the export from the session
291     newdoc.updateFrom(extlock, vamArchive);
292     // LATER: LATER: fix use of updateFrom for file systems where locks cannot be made (because they don't have a lockManager, ie NFS/Unix, etc).
293     vamArchive.unLock();
294     newdoc.unlockFile();
295     log.debug("Transfer complete.");
296   }
297   
298   /**
299    * Creates a VamsasArchive Vobject for accessing and updating document
300    * Note: this will lock the Vamsas Document for exclusive access to the client.
301    * @return session vamsas document
302    * @throws IOException if locks fail or vamsas document read fails.
303    */
304   protected VamsasArchive getVamsasDocument() throws IOException {
305     // TODO: check we haven't already done this once - probably should be done by caller
306     // patiently wait for a lock on the document. (from ArchiveClient.getUpdateable())
307     long tries=5000;
308     while (vamArchive.getLock()==null && --tries>0) {
309 //       Thread.sleep(1);
310         log.debug("Trying to get a document lock for the "+tries+"'th time.");
311       }
312     if (tries==0) 
313       throw new IOException("Failed to get lock for vamsas archive.");
314       
315     VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true, vamArchive);
316
317     return va;
318   }
319   /**
320    * Unlocks the vamsas archive session document after it has been closed.
321    * @throws IOException
322    */
323   protected void unlockVamsasDocument() throws IOException {
324     if (vamArchive!=null)
325       vamArchive.unLock();
326   }
327   /**
328    * create a uniquely named uk.ac.vamsas.client.simpleclient.ClientsFile.addClient(ClientHandle)ile in the session Directory
329    * @see java.io.File.createTempFile
330    * @param pref Prefix for name
331    * @param suff Suffix for name
332    * @return SessionFile object configured for the new file (of length zero)
333    * @throws IOException
334    */
335   protected SessionFile getTempSessionFile(String pref, String suff) throws IOException {
336     File tfile = File.createTempFile(pref,suff,sessionDir);
337     SessionFile tempFile = new SessionFile(tfile);
338     return tempFile;
339   }
340   
341   /**
342    * add a IClient to the session
343    * 
344    * add the client to the client list file
345    * @param client client to add to the session
346    */
347   protected void addClient(IClient client)
348   {
349     if (client == null)
350       slog.error("Try to add a null client to the session ");
351     else {
352       log.debug("Adding client "+client.getClientHandle().getClientUrn());
353       getClientWatcherElement().haltWatch();
354       clist.addClient(client.getClientHandle());
355       getClientWatcherElement().enableWatch();
356       log.debug("Added.");
357     }
358   }
359   
360 /**
361  *  
362  * removes a client from the current session
363  *  removes the client from the session client list
364  *  if the client is the last one from the session  (ClientList), the current session is removed 
365  *  from active session list.
366  *  
367  * @param client client to remove
368  */
369   protected void removeClient(IClient client)
370   {
371     if (client == null)
372       {
373         log.error("Null client passed to removeClient");
374         return;
375       }
376     SessionFileWatcherElement cwe=getClientWatcherElement();
377     if (cwe!=null && cwe.isWatchEnabled()) {
378       cwe.haltWatch();
379     } else {
380       cwe=null;
381     }
382     clist.removeClient(client.getClientHandle(),null);
383     if (this.clist.retrieveClientList() == null|| this.clist.retrieveClientList().length<1)
384       {//assume it is the last client has been removed shutting down session
385         slog.info("last client removed: removing session");
386         log.debug("last client removed: removing session");
387         this.getSessionManager().removeSession(client.getSessionHandle());
388       }
389     else
390     {
391       int active=clist.retrieveClientList().length;
392       log.debug("Still "+active+" active clients");
393       slog.info("Still "+active+" active clients");
394     }
395     if (cwe!=null) {
396       cwe.enableWatch();
397     }
398   }
399 /**
400  * @return the sessionManager
401  */
402 protected SimpleSessionManager getSessionManager() {
403   return sessionManager;
404 }
405 /**
406  * @param sessionManager the sessionManager to set
407  */
408 protected void setSessionManager(SimpleSessionManager sessionManager) {
409   this.sessionManager = sessionManager;
410 }
411 public ClientsFile getStoreDocFile() {
412   if (storedocfile==null) {
413     
414   }
415   return storedocfile;
416 }
417 SessionFileWatcherElement clistWatchElement=null;
418 public SessionFileWatcherElement getClientWatcherElement() {
419   if (clistWatchElement==null) {
420     clistWatchElement=new SessionFileWatcherElement(clist,null);
421   }
422   return clistWatchElement;
423 }
424 }
425
426