1c77d66cf0d993c20da8f03a0c7966b4e03f72fe
[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       this.sessionDir = sessionDir1;//createSessionFiles, need sessionDir attribute set
166       if (!checkSessionFiles(sessionDir1)) 
167         {
168           createSessionFiles();
169         }
170         // session files exist in the directory
171         
172         initSessionObjects();
173         slog.debug("Initialising additional VamsasSession instance");
174         log.debug("Attached to VamsasSession in "+sessionDir1);
175       //} 
176     } else {
177       // start from scratch
178       if (!sessionDir1.mkdir())
179         throw new IOException("Failed to make VamsasSession directory in "+sessionDir1);
180       this.sessionDir = sessionDir1; 
181       createSessionFiles();
182       initSessionObjects();
183       slog.debug("Session directory created.");
184       log.debug("Initialised VamsasSession in "+sessionDir1);
185     }
186   }
187   /**
188    * tests presence of existing sessionfiles files in dir
189    * @param dir
190    * @return
191    */
192   private boolean checkSessionFiles(File dir) throws IOException {
193     File c_file = new File(dir,CLIENT_LIST);
194     File v_doc = new File(dir,VAMSAS_OBJ);
195     if (c_file.exists() && v_doc.exists())
196       return true;
197     return false;
198   }
199   /**
200    * create new empty files in dir
201    *
202    */
203   private void createSessionFiles() throws IOException {
204     if (sessionDir==null)
205       throw new IOException("Invalid call to createSessionFiles() with null sessionDir");
206     File c_file = new File(sessionDir,CLIENT_LIST);
207     File v_doc = new File(sessionDir,VAMSAS_OBJ);
208     if (c_file.createNewFile())
209       log.debug("Created new ClientFile "+c_file); // don't care if this works or not
210     if (v_doc.createNewFile())
211       log.debug("Created new Vamsas Session Document File "+v_doc); 
212   }
213   /**
214    * construct SessionFile objects and watchers for each
215    */
216   private void initSessionObjects() throws IOException {
217     if (clist!=null || vamArchive!=null)
218       throw new IOException("initSessionObjects called for initialised VamsasSession object.");
219     clist = new ClientsFile(new File(sessionDir,CLIENT_LIST));
220     vamArchive = new VamsasFile(new File(sessionDir,VAMSAS_OBJ));
221     initLog();
222   }
223   /**
224    * make a new watcher object for the clientFile
225    * @return new ClientFile watcher instance
226    */
227   public FileWatcher getClientWatcher() {
228     return new FileWatcher(clist.sessionFile);
229   }
230   FileWatcher session_doc_watcher=null;
231   /**
232    * make a new watcher object for the vamsas Document
233    * @return new ClientFile watcher instance
234    */
235   public FileWatcher getDocWatcher() {
236     if (session_doc_watcher==null)
237       return session_doc_watcher = new FileWatcher(vamArchive.sessionFile);
238     return new FileWatcher(vamArchive.sessionFile);
239   }
240   FileWatcher store_doc_file=null;
241   /**
242    * make a new watcher object for the messages file
243    * (thread safe - keeps a reference to the first watcher)
244    * @return new watcher instance
245    */
246   public FileWatcher getStoreWatcher() {
247     if (store_doc_file==null)
248       return store_doc_file = new FileWatcher(new File(CLOSEANDSAVE_FILE));
249     return new FileWatcher(new File(CLOSEANDSAVE_FILE));
250
251   }
252   /**
253    * write to the StoreWatcher file to indicate that a storeDocumentRequest has been made.
254    * The local client's storeWatcher FileWatcher object is updated so the initial change is not registered.
255    * @param client
256    * @param user
257    * @return
258    */
259   public void addStoreDocumentRequest(ClientHandle client, UserHandle user) throws IOException {
260     SessionFile sfw = new SessionFile(new File(sessionDir, CLOSEANDSAVE_FILE));
261     while (!sfw.lockFile())
262       log.debug("Trying to get lock for "+CLOSEANDSAVE_FILE);
263     RandomAccessFile sfwfile=sfw.fileLock.getRaFile();
264     sfwfile.setLength(0); // wipe out any old info.
265     // TODO: rationalise what gets written to this file (ie do we want other clients to read the id of the requestor?)
266     sfwfile.writeUTF(client.getClientUrn()+":"+user.getFullName()+"@"+user.getOrganization());
267     sfw.unlockFile();
268     if (store_doc_file!=null)
269       store_doc_file.setState();
270     slog.info("FinalizeAppData request from "+user.getFullName()+" using "+client.getClientUrn()+"");
271   }
272   /**
273    * create a new session with an existing vamsas Document - by copying it into the session.
274    * @param archive
275    */
276   public void setVamsasDocument(File archive) throws IOException {
277     log.debug("Transferring vamsas data from "+archive+" to session:"+vamArchive.sessionFile);
278     SessionFile xtantdoc = new SessionFile(archive);
279     vamArchive.updateFrom(null, xtantdoc);
280     // LATER: decide if session archive provenance should be updated to reflect access.
281     // TODO: soon! do a proper import objects from external file 
282     log.debug("Transfer complete.");
283   }
284   /**
285    * write session as a new vamsas Document (this will overwrite any existing file without warning)
286    * TODO: test
287    * TODO: verify that lock should be released for vamsas document.
288    * @param destarchive
289    */
290   protected void writeVamsasDocument(File destarchive, Lock extlock) throws IOException {
291     log.debug("Transferring vamsas data from "+vamArchive.sessionFile+" to session:"+destarchive);
292     SessionFile newdoc = new SessionFile(destarchive);
293     if (extlock==null && !vamArchive.lockFile())
294       while (!vamArchive.lockFile())
295         log.info("Trying to get lock for "+vamArchive.sessionFile);
296     // TODO: LATER: decide if session archive provenance should be written in vamsasDocument file for this export.
297     newdoc.updateFrom(extlock, vamArchive);
298     // 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).
299     vamArchive.unLock();
300     newdoc.unlockFile();
301     log.debug("Transfer complete.");
302   }
303   
304   /**
305    * Creates a VamsasArchive Vobject for accessing and updating document
306    * Note: this will lock the Vamsas Document for exclusive access to the client.
307    * @return session vamsas document
308    * @throws IOException if locks fail or vamsas document read fails.
309    */
310   protected VamsasArchive getVamsasDocument() throws IOException {
311     // TODO: check we haven't already done this once
312     if (!vamArchive.lockFile()) 
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    * create a uniquely named uk.ac.vamsas.client.simpleclient.ClientsFile.addClient(ClientHandle)ile in the session Directory
321    * @see java.io.File.createTempFile
322    * @param pref Prefix for name
323    * @param suff Suffix for name
324    * @return SessionFile object configured for the new file (of length zero)
325    * @throws IOException
326    */
327   protected SessionFile getTempSessionFile(String pref, String suff) throws IOException {
328     File tfile = File.createTempFile(pref,suff,sessionDir);
329     SessionFile tempFile = new SessionFile(tfile);
330     return tempFile;
331   }
332   
333   /**
334    * add a IClient to the session
335    * 
336    * add the client to the client list file
337    * @param client client to add to the session
338    */
339   protected void addClient(IClient client)
340   {
341     if (client == null)
342       this.slog.error("Try to add a null client to the session ");
343     else
344       this.clist.addClient(client.getClientHandle(), getClientWatcher().getChangedState());
345   }
346   
347 /**
348  *  
349  * removes a client from the current session
350  *  removes the client from the session client list
351  *  if the client is the last one from the session  (ClientList), the current session is removed 
352  *  from active session list.
353  *  
354  * @param client client to remove
355  */
356   protected void removeClient(IClient client)
357   {
358     if (client == null)
359       {
360       //System.out.println("Try to remove a null client.");
361         this.slog.error("Try to remove a null client.");
362         return;
363       }
364     this.clist.removeClient(client.getClientHandle(), getClientWatcher().getChangedState());
365     if (this.clist.retrieveClientList() == null|| this.clist.retrieveClientList().length<1)
366       {//assume it is the last client has been removed shutting down session
367         System.out.println("last client removed: removing session");
368         this.getSessionManager().removeSession(client.getSessionHandle());
369       }
370     else
371     {
372       this.slog.debug("Still "+this.clist.retrieveClientList().length +" active clients");
373       System.out.println("Still "+(this.clist.retrieveClientList()==null?"null":this.clist.retrieveClientList().length+"") +" active clients");
374     }
375   }
376 /**
377  * @return the sessionManager
378  */
379 protected SimpleSessionManager getSessionManager() {
380   return sessionManager;
381 }
382 /**
383  * @param sessionManager the sessionManager to set
384  */
385 protected void setSessionManager(SimpleSessionManager sessionManager) {
386   this.sessionManager = sessionManager;
387 }
388 }
389
390