1 package uk.ac.vamsas.client.simpleclient;
3 import java.io.BufferedWriter;
5 import java.io.IOException;
6 import java.io.PrintStream;
7 import java.io.PrintWriter;
8 import java.io.RandomAccessFile;
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;
17 import uk.ac.vamsas.client.ClientHandle;
18 import uk.ac.vamsas.client.IClient;
19 import uk.ac.vamsas.client.UserHandle;
21 * Does all the IO operations for a SimpleClient instance accessing
22 * a SimpleClient vamsas session.
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.
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).
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.
45 * - vamsasdocument.xml : core info
47 * - <applicationname>.version.sessionnumber.raw (string given in vamsasdocument.xml applicationData entry)
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.
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
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.
64 * - Update watcher checks for file change -
66 * Procedures for file based session message exchange
67 * - session document modification flag
71 public class VamsasSession {
73 * indicator file for informing other processes that
74 * they should finalise their vamsas datasets for
75 * storing into a vamsas archive.
77 public static final String CLOSEANDSAVE_FILE="stored.log";
79 * session file storing the last_stored_stat data
81 public static final String MODIFIEDDOC_FILE="modified";
84 private SimpleSessionManager sessionManager = null;
87 * called to clear update flag after a successful offline storage event
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);
95 * called to indicate session document has been modified.
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);
105 * @return true if session document has been modified since last offline storage event
107 protected boolean getUnsavedFlag() {
108 SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
109 return laststored.checkFlag();
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");
118 * setup the sessionLog using Log4j.
119 * @throws IOException
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()));
126 //Appender app = slog.getAppender("SESSION_LOG");
127 if (app == null) log.info("No appender for SESSION_LOG");
129 if (slog!= null && app != null)
131 if (app instanceof FileAppender)
133 File sessionLogFile = new File(this.sessionDir, ((FileAppender)app).getFile());
134 slog.addAppender(new FileAppender(app.getLayout(), sessionLogFile.getAbsolutePath()));
136 // slog.removeAppender("SESSION_LOG");
141 * the sessionDir is given as the session location for new clients.
143 protected File sessionDir;
145 * holds the list of attached clients
148 public static final String CLIENT_LIST="Clients.obj";
152 VamsasFile vamArchive;
153 public static final String VAMSAS_OBJ="VamDoc.jar";
156 * sets up the vamsas session files and watchers in sessionDir
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))
168 createSessionFiles();
170 // session files exist in the directory
172 initSessionObjects();
173 slog.debug("Initialising additional VamsasSession instance");
174 log.debug("Attached to VamsasSession in "+sessionDir1);
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);
188 * tests presence of existing sessionfiles files in dir
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())
200 * create new empty files in dir
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);
214 * construct SessionFile objects and watchers for each
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));
224 * make a new watcher object for the clientFile
225 * @return new ClientFile watcher instance
227 public FileWatcher getClientWatcher() {
228 return new FileWatcher(clist.sessionFile);
230 FileWatcher session_doc_watcher=null;
232 * make a new watcher object for the vamsas Document
233 * @return new ClientFile watcher instance
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);
240 FileWatcher store_doc_file=null;
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
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));
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.
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());
268 if (store_doc_file!=null)
269 store_doc_file.setState();
270 slog.info("FinalizeAppData request from "+user.getFullName()+" using "+client.getClientUrn()+"");
273 * create a new session with an existing vamsas Document - by copying it into the session.
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.");
285 * write session as a new vamsas Document (this will overwrite any existing file without warning)
287 * TODO: verify that lock should be released for vamsas document.
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).
301 log.debug("Transfer complete.");
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.
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.");
315 VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true, vamArchive);
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
327 protected SessionFile getTempSessionFile(String pref, String suff) throws IOException {
328 File tfile = File.createTempFile(pref,suff,sessionDir);
329 SessionFile tempFile = new SessionFile(tfile);
334 * add a IClient to the session
336 * add the client to the client list file
337 * @param client client to add to the session
339 protected void addClient(IClient client)
342 this.slog.error("Try to add a null client to the session ");
344 this.clist.addClient(client.getClientHandle(), getClientWatcher().getChangedState());
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.
354 * @param client client to remove
356 protected void removeClient(IClient client)
360 //System.out.println("Try to remove a null client.");
361 this.slog.error("Try to remove a null client.");
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());
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");
377 * @return the sessionManager
379 protected SimpleSessionManager getSessionManager() {
380 return sessionManager;
383 * @param sessionManager the sessionManager to set
385 protected void setSessionManager(SimpleSessionManager sessionManager) {
386 this.sessionManager = sessionManager;