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