a70fe54fa6dc0633fc9088634a64e3f48f3e6fae
[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   
123   /**
124    * the sessionDir is given as the session location for new clients.
125    */
126   protected File sessionDir;
127   /**
128    * holds the list of attached clients
129    */
130   ClientsFile clist;
131   public static final String CLIENT_LIST="Clients.obj";
132   /**
133    * holds the data
134    */
135   VamsasFile vamArchive; 
136   public static final String VAMSAS_OBJ="VamDoc.jar";
137   
138   /**
139    * sets up the vamsas session files and watchers in sessionDir
140    * @param sessionDir
141    */
142   protected VamsasSession(File sessionDir) throws IOException {
143     if (sessionDir==null)
144       throw new Error("Null directory for VamsasSession.");
145     if (sessionDir.exists()) {
146       if (!sessionDir.isDirectory() || !sessionDir.canWrite() || !sessionDir.canRead())
147         throw new IOException("Cannot access '"+sessionDir+"' as a read/writable Directory.");
148       if (checkSessionFiles(sessionDir)) {
149         // session files exist in the directory
150         this.sessionDir = sessionDir;
151         initSessionObjects();
152         slog.debug("Initialising additional VamsasSession instance");
153         log.debug("Attached to VamsasSession in "+sessionDir);
154       } 
155     } else {
156       // start from scratch
157       if (!sessionDir.mkdir())
158         throw new IOException("Failed to make VamsasSession directory in "+sessionDir);
159       this.sessionDir = sessionDir; 
160       createSessionFiles();
161       initSessionObjects();
162       slog.debug("Session directory created.");
163       log.debug("Initialised VamsasSession in "+sessionDir);
164     }
165   }
166   /**
167    * tests presence of existing sessionfiles files in dir
168    * @param dir
169    * @return
170    */
171   private boolean checkSessionFiles(File dir) throws IOException {
172     File c_file = new File(dir,CLIENT_LIST);
173     File v_doc = new File(dir,VAMSAS_OBJ);
174     if (c_file.exists() && v_doc.exists())
175       return true;
176     return false;
177   }
178   /**
179    * create new empty files in dir
180    *
181    */
182   private void createSessionFiles() throws IOException {
183     if (sessionDir==null)
184       throw new IOException("Invalid call to createSessionFiles() with null sessionDir");
185     File c_file = new File(sessionDir,CLIENT_LIST);
186     File v_doc = new File(sessionDir,VAMSAS_OBJ);
187     if (c_file.createNewFile())
188       log.debug("Created new ClientFile "+c_file); // don't care if this works or not
189     if (v_doc.createNewFile())
190       log.debug("Created new Vamsas Session Document File "+v_doc); 
191   }
192   /**
193    * construct SessionFile objects and watchers for each
194    */
195   private void initSessionObjects() throws IOException {
196     if (clist!=null || vamArchive!=null)
197       throw new IOException("initSessionObjects called for initialised VamsasSession object.");
198     clist = new ClientsFile(new File(sessionDir,CLIENT_LIST));
199     vamArchive = new VamsasFile(new File(sessionDir,VAMSAS_OBJ));
200     initLog();
201   }
202   /**
203    * make a new watcher object for the clientFile
204    * @return new ClientFile watcher instance
205    */
206   public FileWatcher getClientWatcher() {
207     return new FileWatcher(clist.sessionFile);
208   }
209   FileWatcher session_doc_watcher=null;
210   /**
211    * make a new watcher object for the vamsas Document
212    * @return new ClientFile watcher instance
213    */
214   public FileWatcher getDocWatcher() {
215     if (session_doc_watcher==null)
216       return session_doc_watcher = new FileWatcher(vamArchive.sessionFile);
217     return new FileWatcher(vamArchive.sessionFile);
218   }
219   FileWatcher store_doc_file=null;
220   /**
221    * make a new watcher object for the messages file
222    * (thread safe - keeps a reference to the first watcher)
223    * @return new watcher instance
224    */
225   public FileWatcher getStoreWatcher() {
226     if (store_doc_file==null)
227       return store_doc_file = new FileWatcher(new File(CLOSEANDSAVE_FILE));
228     return new FileWatcher(new File(CLOSEANDSAVE_FILE));
229
230   }
231   /**
232    * write to the StoreWatcher file to indicate that a storeDocumentRequest has been made.
233    * The local client's storeWatcher FileWatcher object is updated so the initial change is not registered.
234    * @param client
235    * @param user
236    * @return
237    */
238   public void addStoreDocumentRequest(ClientHandle client, UserHandle user) throws IOException {
239     SessionFile sfw = new SessionFile(new File(sessionDir, CLOSEANDSAVE_FILE));
240     while (!sfw.lockFile())
241       log.debug("Trying to get lock for "+CLOSEANDSAVE_FILE);
242     RandomAccessFile sfwfile=sfw.fileLock.getRaFile();
243     sfwfile.setLength(0); // wipe out any old info.
244     // TODO: rationalise what gets written to this file (ie do we want other clients to read the id of the requestor?)
245     sfwfile.writeUTF(client.getClientUrn()+":"+user.getFullName()+"@"+user.getOrganization());
246     sfw.unlockFile();
247     if (store_doc_file!=null)
248       store_doc_file.setState();
249     slog.info("FinalizeAppData request from "+user.getFullName()+" using "+client.getClientUrn()+"");
250   }
251   /**
252    * create a new session with an existing vamsas Document - by copying it into the session.
253    * @param archive
254    */
255   public void setVamsasDocument(File archive) throws IOException {
256     log.debug("Transferring vamsas data from "+archive+" to session:"+vamArchive.sessionFile);
257     SessionFile xtantdoc = new SessionFile(archive);
258     vamArchive.updateFrom(null, xtantdoc);
259     // LATER: decide if session archive provenance should be updated to reflect access.
260     // TODO: soon! do a proper import objects from external file 
261     log.debug("Transfer complete.");
262   }
263   /**
264    * write session as a new vamsas Document (this will overwrite any existing file without warning)
265    * TODO: test
266    * TODO: verify that lock should be released for vamsas document.
267    * @param destarchive
268    */
269   protected void writeVamsasDocument(File destarchive, Lock extlock) throws IOException {
270     log.debug("Transferring vamsas data from "+vamArchive.sessionFile+" to session:"+destarchive);
271     SessionFile newdoc = new SessionFile(destarchive);
272     if (extlock==null && !vamArchive.lockFile())
273       while (!vamArchive.lockFile())
274         log.info("Trying to get lock for "+vamArchive.sessionFile);
275     // TODO: LATER: decide if session archive provenance should be written in vamsasDocument file for this export.
276     newdoc.updateFrom(extlock, vamArchive);
277     // 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).
278     vamArchive.unLock();
279     newdoc.unlockFile();
280     log.debug("Transfer complete.");
281   }
282   
283   /**
284    * Creates a VamsasArchive Vobject for accessing and updating document
285    * Note: this will lock the Vamsas Document for exclusive access to the client.
286    * @return session vamsas document
287    * @throws IOException if locks fail or vamsas document read fails.
288    */
289   protected VamsasArchive getVamsasDocument() throws IOException {
290     // TODO: check we haven't already done this once
291     if (!vamArchive.lockFile()) 
292       throw new IOException("Failed to get lock for vamsas archive.");
293     
294     VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true, vamArchive);
295     
296     return va;
297   }
298   /**
299    * create a uniquely named file in the session Directory
300    * @see java.io.File.createTempFile
301    * @param pref Prefix for name
302    * @param suff Suffix for name
303    * @return SessionFile object configured for the new file (of length zero)
304    * @throws IOException
305    */
306   protected SessionFile getTempSessionFile(String pref, String suff) throws IOException {
307     File tfile = File.createTempFile(pref,suff,sessionDir);
308     SessionFile tempFile = new SessionFile(tfile);
309     return tempFile;
310   }
311 }
312
313