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