497431db9fc6c22c49bbad5a59c665d7e4226bd4
[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          * extant archive IO handler
299          */
300   VamsasArchive _va=null;
301   /**
302    * Creates a VamsasArchive Vobject for accessing and updating document
303    * Note: this will lock the Vamsas Document for exclusive access to the client.
304    * @return session vamsas document
305    * @throws IOException if locks fail or vamsas document read fails.
306    */
307   protected VamsasArchive getVamsasDocument() throws IOException {
308     // check we haven't already done this once - probably should be done by caller
309     if (_va!=null)
310       return _va;
311     // patiently wait for a lock on the document. (from ArchiveClient.getUpdateable())
312     long tries=5000;
313     while (vamArchive.getLock()==null && --tries>0) {
314 //       Thread.sleep(1);
315         log.debug("Trying to get a document lock for the "+tries+"'th time.");
316       }
317     if (tries==0) 
318       throw new IOException("Failed to get lock for vamsas archive.");
319       
320     VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true, vamArchive);
321
322     return va;
323   }
324   /**
325    * Unlocks the vamsas archive session document after it has been closed.
326    * @throws IOException
327    */
328   protected void unlockVamsasDocument() throws IOException {
329     if (_va!=null)
330       _va.closeArchive();
331     _va=null;
332     if (vamArchive!=null)
333       vamArchive.unLock();
334     
335   }
336   /**
337    * create a uniquely named uk.ac.vamsas.client.simpleclient.ClientsFile.addClient(ClientHandle)ile in the session Directory
338    * @see java.io.File.createTempFile
339    * @param pref Prefix for name
340    * @param suff Suffix for name
341    * @return SessionFile object configured for the new file (of length zero)
342    * @throws IOException
343    */
344   protected SessionFile getTempSessionFile(String pref, String suff) throws IOException {
345     File tfile = File.createTempFile(pref,suff,sessionDir);
346     SessionFile tempFile = new SessionFile(tfile);
347     return tempFile;
348   }
349   
350   /**
351    * add a IClient to the session
352    * 
353    * add the client to the client list file
354    * @param client client to add to the session
355    */
356   protected void addClient(IClient client)
357   {
358     if (client == null)
359       slog.error("Try to add a null client to the session ");
360     else {
361       log.debug("Adding client "+client.getClientHandle().getClientUrn());
362       getClientWatcherElement().haltWatch();
363       clist.addClient(client.getClientHandle());
364       getClientWatcherElement().enableWatch();
365       log.debug("Added.");
366     }
367   }
368   
369 /**
370  *  
371  * removes a client from the current session
372  *  removes the client from the session client list
373  *  if the client is the last one from the session  (ClientList), the current session is removed 
374  *  from active session list.
375  *  
376  * @param client client to remove
377  */
378   protected void removeClient(IClient client)
379   {
380     if (client == null)
381       {
382         log.error("Null client passed to removeClient");
383         return;
384       }
385     SessionFileWatcherElement cwe=getClientWatcherElement();
386     if (cwe!=null && cwe.isWatchEnabled()) {
387       cwe.haltWatch();
388     };
389     clist.removeClient(client.getClientHandle(),null);
390     if (this.clist.retrieveClientList() == null|| this.clist.retrieveClientList().length<1)
391       {//assume it is the last client has been removed shutting down session
392         slog.info("last client removed: removing session");
393         log.debug("last client removed: removing session");
394         this.getSessionManager().removeSession(client.getSessionHandle());
395       }
396     else
397     {
398       int active=clist.retrieveClientList().length;
399       log.debug("Still "+active+" active clients");
400       slog.info("Still "+active+" active clients");
401     }
402     if (cwe!=null) {
403       cwe.enableWatch();
404     }
405   }
406 /**
407  * @return the sessionManager
408  */
409 protected SimpleSessionManager getSessionManager() {
410   return sessionManager;
411 }
412 /**
413  * @param sessionManager the sessionManager to set
414  */
415 protected void setSessionManager(SimpleSessionManager sessionManager) {
416   this.sessionManager = sessionManager;
417 }
418 public ClientsFile getStoreDocFile() {
419   if (storedocfile==null) {
420     
421   }
422   return storedocfile;
423 }
424 SessionFileWatcherElement clistWatchElement=null;
425 public SessionFileWatcherElement getClientWatcherElement() {
426   if (clistWatchElement==null) {
427     clistWatchElement=new SessionFileWatcherElement(clist,null);
428   }
429   return clistWatchElement;
430 }
431 }
432
433