VamsasSession Fix session constructor/initialisation if the session directory already...
[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.UserHandle;
19 /**
20  * Does all the IO operations for a SimpleClient instance accessing 
21  * a SimpleClient vamsas session.
22  * 
23  * Basically, it defines the various standard names for the files 
24  * in the session directory (that maps to the sessionUrn), 
25  * provides constructors for the file handlers and watchers of 
26  * those file entities, and some higher level methods 
27  * to check and change the state flags for the session.
28  * 
29  * TODO: move the stuff below to the SimpleClientFactory documentation.
30  * much may not be valid now :
31  * Vamsas client is intialised with a path to create live session directories. 
32  * This path may contain a vamsas.properties file 
33  * that sets additional parameters (otherwise client 
34  * just uses the one on the classpath).
35  * 
36  * A vamsas session consists of :
37  *  SessionDir - translates to urn of a live session.
38  *  Contains: Vamsas Document (as a jar), Session client list file, 
39  *  both of which may be locked, and additional 
40  *  temporary versions of these files when write 
41  *  operations are taking place.
42  * 
43  * Zip file entries
44  *  - vamsasdocument.xml : core info
45  *  one or more:
46  *  - <applicationname>.version.sessionnumber.raw (string given in vamsasdocument.xml applicationData entry)
47  *  
48  * Lockfile
49  *  - filename given in the vamsasdocument.xml. Should be checked for validity by any client and rewritten if necessary. 
50  *    The lockfile can point to the jar itself.
51  * Mode of operation.
52  * Initially - documentHandler either:
53  *  - creates a zip for a new session for the client
54  *  - connect to an existing session zip 
55  *   1. reads session urn file
56  *   2. waits for lock
57  *   3. examines session - decide whether to create new application data slice or connect to one stored in session.
58  *   4. writes info into session file
59  *   5. releases lock and generates local client events.
60  *   6. Creates Watcher thread to generate events.
61  * 
62  * During the session
63  *  - Update watcher checks for file change - 
64  * 
65  * Procedures for file based session message exchange
66  *  - session document modification flag
67  *    
68  */
69
70 public class VamsasSession {
71   /**
72    * indicator file for informing other processes that 
73    * they should finalise their vamsas datasets for 
74    * storing into a vamsas archive.
75    */
76   public static final String CLOSEANDSAVE_FILE="stored.log";
77   /**
78    * session file storing the last_stored_stat data 
79    */
80   public static final String MODIFIEDDOC_FILE="modified";
81
82   /**
83    * called to clear update flag after a successful offline storage event
84    */
85   protected void clearUnsavedFlag() {
86     SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
87     if (!laststored.clearFlag())
88       log.warn("Unsaved flag was not cleared for "+sessionDir);
89   }
90   /**
91    * called to indicate session document has been modified.
92    *
93    */
94   protected void setUnsavedFlag() {
95     SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
96     if (!laststored.setFlag())
97       log.warn("Couldn't set the Unsaved flag for "+sessionDir);
98   }
99   /**
100    * 
101    * @return true if session document has been modified since last offline storage event 
102    */
103   protected boolean getUnsavedFlag() {
104     SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
105     return laststored.checkFlag();
106   }
107   /**
108    * log file location
109    */
110   public static final String SESSION_LOG="Log.txt";
111   private static Log log = LogFactory.getLog(VamsasSession.class);
112   protected Logger slog = Logger.getLogger("uk.ac.vamsas.client.SessionLog");
113   /**
114    * setup the sessionLog using Log4j.
115    * @throws IOException
116    */
117   private void initLog() throws IOException {
118     // LATER: make dedicated appender format for session log.
119     Appender app = slog.getAppender("SESSION_LOG");
120    // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
121     
122     //Appender app = slog.getAppender("SESSION_LOG");
123     if (app == null) log.info("No appender for SESSION_LOG");
124   
125     if (slog!= null && app != null)
126       {
127         if (app instanceof FileAppender)
128           {
129             File sessionLogFile =  new File(this.sessionDir, ((FileAppender)app).getFile());
130             slog.addAppender(new FileAppender(app.getLayout(), sessionLogFile.getAbsolutePath()));
131           }
132         // slog.removeAppender("SESSION_LOG");
133       }
134   }
135   
136   /**
137    * the sessionDir is given as the session location for new clients.
138    */
139   protected File sessionDir;
140   /**
141    * holds the list of attached clients
142    */
143   ClientsFile clist;
144   public static final String CLIENT_LIST="Clients.obj";
145   /**
146    * holds the data
147    */
148   VamsasFile vamArchive; 
149   public static final String VAMSAS_OBJ="VamDoc.jar";
150   
151   /**
152    * sets up the vamsas session files and watchers in sessionDir
153    * @param sessionDir1
154    */
155   protected VamsasSession(File sessionDir1) throws IOException {
156     if (sessionDir1==null)
157       throw new Error("Null directory for VamsasSession.");
158     if (sessionDir1.exists()) {
159       if (!sessionDir1.isDirectory() || !sessionDir1.canWrite() || !sessionDir1.canRead())
160         throw new IOException("Cannot access '"+sessionDir1+"' as a read/writable Directory.");
161       if (checkSessionFiles(sessionDir1)) {
162         createSessionFiles();
163       }
164         // session files exist in the directory
165         this.sessionDir = sessionDir1;
166         initSessionObjects();
167         slog.debug("Initialising additional VamsasSession instance");
168         log.debug("Attached to VamsasSession in "+sessionDir1);
169       //} 
170     } else {
171       // start from scratch
172       if (!sessionDir1.mkdir())
173         throw new IOException("Failed to make VamsasSession directory in "+sessionDir1);
174       this.sessionDir = sessionDir1; 
175       createSessionFiles();
176       initSessionObjects();
177       slog.debug("Session directory created.");
178       log.debug("Initialised VamsasSession in "+sessionDir1);
179     }
180   }
181   /**
182    * tests presence of existing sessionfiles files in dir
183    * @param dir
184    * @return
185    */
186   private boolean checkSessionFiles(File dir) throws IOException {
187     File c_file = new File(dir,CLIENT_LIST);
188     File v_doc = new File(dir,VAMSAS_OBJ);
189     if (c_file.exists() && v_doc.exists())
190       return true;
191     return false;
192   }
193   /**
194    * create new empty files in dir
195    *
196    */
197   private void createSessionFiles() throws IOException {
198     if (sessionDir==null)
199       throw new IOException("Invalid call to createSessionFiles() with null sessionDir");
200     File c_file = new File(sessionDir,CLIENT_LIST);
201     File v_doc = new File(sessionDir,VAMSAS_OBJ);
202     if (c_file.createNewFile())
203       log.debug("Created new ClientFile "+c_file); // don't care if this works or not
204     if (v_doc.createNewFile())
205       log.debug("Created new Vamsas Session Document File "+v_doc); 
206   }
207   /**
208    * construct SessionFile objects and watchers for each
209    */
210   private void initSessionObjects() throws IOException {
211     if (clist!=null || vamArchive!=null)
212       throw new IOException("initSessionObjects called for initialised VamsasSession object.");
213     clist = new ClientsFile(new File(sessionDir,CLIENT_LIST));
214     vamArchive = new VamsasFile(new File(sessionDir,VAMSAS_OBJ));
215     initLog();
216   }
217   /**
218    * make a new watcher object for the clientFile
219    * @return new ClientFile watcher instance
220    */
221   public FileWatcher getClientWatcher() {
222     return new FileWatcher(clist.sessionFile);
223   }
224   FileWatcher session_doc_watcher=null;
225   /**
226    * make a new watcher object for the vamsas Document
227    * @return new ClientFile watcher instance
228    */
229   public FileWatcher getDocWatcher() {
230     if (session_doc_watcher==null)
231       return session_doc_watcher = new FileWatcher(vamArchive.sessionFile);
232     return new FileWatcher(vamArchive.sessionFile);
233   }
234   FileWatcher store_doc_file=null;
235   /**
236    * make a new watcher object for the messages file
237    * (thread safe - keeps a reference to the first watcher)
238    * @return new watcher instance
239    */
240   public FileWatcher getStoreWatcher() {
241     if (store_doc_file==null)
242       return store_doc_file = new FileWatcher(new File(CLOSEANDSAVE_FILE));
243     return new FileWatcher(new File(CLOSEANDSAVE_FILE));
244
245   }
246   /**
247    * write to the StoreWatcher file to indicate that a storeDocumentRequest has been made.
248    * The local client's storeWatcher FileWatcher object is updated so the initial change is not registered.
249    * @param client
250    * @param user
251    * @return
252    */
253   public void addStoreDocumentRequest(ClientHandle client, UserHandle user) throws IOException {
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 session archive provenance should be written in vamsasDocument file for this export.
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
306     if (!vamArchive.lockFile()) 
307       throw new IOException("Failed to get lock for vamsas archive.");
308     
309     VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true, vamArchive);
310     
311     return va;
312   }
313   /**
314    * create a uniquely named file in the session Directory
315    * @see java.io.File.createTempFile
316    * @param pref Prefix for name
317    * @param suff Suffix for name
318    * @return SessionFile object configured for the new file (of length zero)
319    * @throws IOException
320    */
321   protected SessionFile getTempSessionFile(String pref, String suff) throws IOException {
322     File tfile = File.createTempFile(pref,suff,sessionDir);
323     SessionFile tempFile = new SessionFile(tfile);
324     return tempFile;
325   }
326 }
327
328