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