1 package uk.ac.vamsas.client.simpleclient;
4 import java.io.IOException;
5 import java.io.RandomAccessFile;
7 import org.apache.commons.logging.Log;
8 import org.apache.commons.logging.LogFactory;
9 import org.apache.log4j.FileAppender;
10 import org.apache.log4j.Logger;
11 import org.apache.log4j.PatternLayout;
13 import uk.ac.vamsas.client.ClientHandle;
14 import uk.ac.vamsas.client.Events;
15 import uk.ac.vamsas.client.IClient;
16 import uk.ac.vamsas.client.UserHandle;
18 * Does all the IO operations for a SimpleClient instance accessing
19 * a SimpleClient vamsas session.
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.
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).
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.
42 * - vamsasdocument.xml : core info
44 * - <applicationname>.version.sessionnumber.raw (string given in vamsasdocument.xml applicationData entry)
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.
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
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.
61 * - Update watcher checks for file change -
63 * Procedures for file based session message exchange
64 * - session document modification flag
68 public class VamsasSession {
70 * indicator file for informing other processes that
71 * they should finalise their vamsas datasets for
72 * storing into a vamsas archive.
74 public static final String CLOSEANDSAVE_FILE="stored.log";
76 * session file storing the last_stored_stat data
78 public static final String MODIFIEDDOC_FILE="modified";
81 private SimpleSessionManager sessionManager = null;
84 * Count of cycles before considering the current client as the last one of the session (if no other client registered as active )
86 private final int watchCycleCountBeforeLastClient = 2 ;
89 * time between checking
91 public int WATCH_SLEEP=30;
94 * called to clear update flag after a successful offline storage event
96 protected void clearUnsavedFlag() {
97 SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
98 if (!laststored.clearFlag())
99 log.warn("Unsaved flag was not cleared for "+sessionDir);
102 * called to indicate session document has been modified.
105 protected void setUnsavedFlag() {
106 SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
107 if (!laststored.setFlag())
108 log.warn("Couldn't set the Unsaved flag for "+sessionDir);
112 * @return true if session document has been modified since last offline storage event
114 protected boolean getUnsavedFlag() {
115 SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
116 return laststored.checkFlag();
121 public static final String SESSION_LOG="Log.txt";
122 private static Log log = LogFactory.getLog(VamsasSession.class);
123 protected Logger slog = Logger.getLogger("uk.ac.vamsas.client.SessionLog");
125 * setup the sessionLog using Log4j.
126 * @throws IOException
128 private void initLog() throws IOException {
129 // TODO: fix session event logging
130 // LATER: make dedicated appender format for session log.
131 /*Appender app = slog.getAppender("log4j.appender.SESSIONLOG");
132 // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
133 // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
134 for (Enumeration e = slog.getAllAppenders() ; e.hasMoreElements() ;) {
135 System.out.println(e.nextElement());
140 File sessionLogFile = new File(this.sessionDir, SESSION_LOG);
141 slog.addAppender(new FileAppender(new PatternLayout("%-4r [%t] %-5p %c %x - %m%n"), sessionLogFile.getAbsolutePath(), true));
143 log.info("No appender for SessionLog");
148 * the sessionDir is given as the session location for new clients.
150 protected File sessionDir;
152 * holds the list of attached clients
155 public static final String CLIENT_LIST="Clients.obj";
159 VamsasFile vamArchive;
160 public static final String VAMSAS_OBJ="VamDoc.jar";
163 * sets up the vamsas session files and watchers in sessionDir
166 protected VamsasSession(File sessionDir1) throws IOException {
167 if (sessionDir1==null)
168 throw new Error("Null directory for VamsasSession.");
169 if (sessionDir1.exists()) {
170 if (!sessionDir1.isDirectory() || !sessionDir1.canWrite() || !sessionDir1.canRead())
171 throw new IOException("Cannot access '"+sessionDir1+"' as a read/writable Directory.");
172 if (!checkSessionFiles(sessionDir1))
173 log.warn("checkSessionFiles() returned false. Possible client implementation error");
174 this.sessionDir = sessionDir1;
175 initSessionObjects();
176 slog.debug("Initialising additional VamsasSession instance");
177 log.debug("Attached to VamsasSession in "+sessionDir1);
180 // start from scratch
181 if (!sessionDir1.mkdir())
182 throw new IOException("Failed to make VamsasSession directory in "+sessionDir1);
183 createSessionFiles();
184 initSessionObjects();
185 slog.debug("Session directory created.");
186 log.debug("Initialised VamsasSession in "+sessionDir1);
190 * tests presence of existing sessionfiles files in dir
194 private boolean checkSessionFiles(File dir) throws IOException {
195 File c_file = new File(dir,CLIENT_LIST);
196 File v_doc = new File(dir,VAMSAS_OBJ);
197 if (c_file.exists() && v_doc.exists())
202 * create new empty files in dir
205 private void createSessionFiles() throws IOException {
206 if (sessionDir==null)
207 throw new IOException("Invalid call to createSessionFiles() with null sessionDir");
208 File c_file = new File(sessionDir,CLIENT_LIST);
209 File v_doc = new File(sessionDir,VAMSAS_OBJ);
210 if (!c_file.exists() && c_file.createNewFile())
211 log.debug("Created new ClientFile "+c_file); // don't care if this works or not
212 if (!v_doc.exists() && v_doc.createNewFile())
213 log.debug("Created new Vamsas Session Document File "+v_doc);
216 * construct SessionFile objects and watchers for each
218 private void initSessionObjects() throws IOException {
219 createSessionFiles();
220 if (clist!=null || vamArchive!=null)
221 throw new IOException("initSessionObjects called for initialised VamsasSession object.");
222 clist = new ClientsFile(new File(sessionDir,CLIENT_LIST));
223 vamArchive = new VamsasFile(new File(sessionDir,VAMSAS_OBJ));
224 storedocfile=new ClientsFile(new File(sessionDir, CLOSEANDSAVE_FILE));
228 * make a new watcher object for the clientFile
229 * @return new ClientFile watcher instance
231 public FileWatcher getClientWatcher() {
232 return new FileWatcher(clist.sessionFile);
235 * make a new watcher object for the vamsas Document
236 * @return new ClientFile watcher instance
238 public FileWatcher getDocWatcher() {
239 return new FileWatcher(vamArchive.sessionFile);
241 FileWatcher store_doc_file=null;
242 public ClientsFile storedocfile=null;
244 * make a new watcher object for the messages file
245 * @return new watcher instance
247 public FileWatcher getStoreWatcher() {
248 return new FileWatcher(new File(sessionDir,CLOSEANDSAVE_FILE));
252 * write to the StoreWatcher file to indicate that a storeDocumentRequest has been made.
253 * The local client's storeWatcher FileWatcher object is updated so the initial change is not registered.
258 public void addStoreDocumentRequest(ClientHandle client, UserHandle user) throws IOException {
259 // TODO: replace this with clientsFile mechanism
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 a provenance entry should be written in the exported document recording the export from the session
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.");
304 * extant archive IO handler
306 VamsasArchive _va=null;
308 * Creates a VamsasArchive Vobject for accessing and updating document
309 * Note: this will lock the Vamsas Document for exclusive access to the client.
310 * @return session vamsas document
311 * @throws IOException if locks fail or vamsas document read fails.
313 protected VamsasArchive getVamsasDocument() throws IOException {
314 // check we haven't already done this once - probably should be done by caller
317 // patiently wait for a lock on the document. (from ArchiveClient.getUpdateable())
319 while (vamArchive.getLock()==null && --tries>0) {
321 log.debug("Trying to get a document lock for the "+tries+"'th time.");
324 throw new IOException("Failed to get lock for vamsas archive.");
326 VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true, vamArchive);
331 * Unlocks the vamsas archive session document after it has been closed.
332 * @throws IOException
334 protected void unlockVamsasDocument() throws IOException {
338 if (vamArchive!=null)
343 * create a uniquely named uk.ac.vamsas.client.simpleclient.ClientsFile.addClient(ClientHandle)ile in the session Directory
344 * @see java.io.File.createTempFile
345 * @param pref Prefix for name
346 * @param suff Suffix for name
347 * @return SessionFile object configured for the new file (of length zero)
348 * @throws IOException
350 protected SessionFile getTempSessionFile(String pref, String suff) throws IOException {
351 File tfile = File.createTempFile(pref,suff,sessionDir);
352 SessionFile tempFile = new SessionFile(tfile);
357 * add a IClient to the session
359 * add the client to the client list file
360 * @param client client to add to the session
362 protected void addClient(IClient client)
365 slog.error("Try to add a null client to the session ");
367 log.debug("Adding client "+client.getClientHandle().getClientUrn());
368 getClientWatcherElement().haltWatch();
369 clist.addClient(client.getClientHandle());
370 getClientWatcherElement().enableWatch();
372 log.debug("Register Client as Active.");
374 //tracks modification to the client list and readds client to the list
375 getClientWatcherElement().setHandler(new AddClientWatchCallBack(client));
380 * Handler for the client watcher.
382 * If (the current client is not in the client list, it is added again;)
384 private class AddClientWatchCallBack implements WatcherCallBack
387 private IClient client ;
390 *Inits the handler with the client to check in the list
391 * @param client client to monitor in the client list
393 protected AddClientWatchCallBack (IClient client)
395 this.client = client;
399 * If the client list is modified, checks if the current is still in the list. otherwise, readds ti.
400 * @return true to enable watcher, or false to disable it in future WatcherThread cycles.
402 public boolean handleWatchEvent(WatcherElement watcher, Lock lock)
404 boolean isWatchEnable = watcher.isWatchEnabled();
405 if (lock== null)//no update on the list
406 return isWatchEnable;
411 //checks if the client is not already in the lists
412 ClientHandle[] cl = clist.retrieveClientList(lock);//clist.retrieveClientList();
413 boolean found = false;
416 for (int chi = cl.length-1; !found && chi > -1; chi--) {
417 found = cl[chi].equals(this.client.getClientHandle());
422 if( log.isDebugEnabled())
423 log.debug("the client has not been found in the list. Adding it again :"+cl);
429 return isWatchEnable;
435 * removes a client from the current session
436 * removes the client from the session client list
437 * if the client is the last one from the session (ClientList), the current session is removed
438 * from active session list.
440 * @param client client to remove
442 protected void removeClient(SimpleClient client)//IClient client)
446 log.error("Null client passed to removeClient");
449 ClientSessionFileWatcherElement cwe=getClientWatcherElement();
450 if (cwe!=null && cwe.isWatchEnabled()) {
453 //set handler to check is the the last active client of the session
454 //Wait for several watchers cycle to see if the current client was the last client active in the session.
455 //if yes, close the session
457 getClientWatcherElement().setHandler(new RemoveClientWatchCallBack (client));
458 getClientWatcherElement().setTimeoutBeforeLastCycle(this.watchCycleCountBeforeLastClient);
459 log.info("remove client from list");
467 /*clist.removeClient(client.getClientHandle(),null);
468 if (this.clist.retrieveClientList() == null|| this.clist.retrieveClientList().length<1)
469 {//assume it is the last client has been removed shutting down session
470 slog.info("last client removed: removing session");
471 log.debug("last client removed: removing session");
472 this.getSessionManager().removeSession(client.getSessionHandle());
476 int active=clist.retrieveClientList().length;
477 log.debug("Still "+active+" active clients");
478 slog.info("Still "+active+" active clients");
484 * Handler for the client watcher. after a client have been removed
486 * Checks if the client is not the last active one.
488 * If (the current client is not in the client list readd it;)
490 private class RemoveClientWatchCallBack implements WatcherCallBack
493 private SimpleClient client ;
496 *Inits the handler with the client to check in the list
497 * @param client client to monitor in the client list
499 protected RemoveClientWatchCallBack (SimpleClient client)
501 this.client = client;
505 * If the client list is modified, checks if the current is still in the list. otherwise, readds ti.
506 * @return true to enable watcher, or false to disable it in future WatcherThread cycles.
508 public boolean handleWatchEvent(WatcherElement watcher, Lock lock)
510 // if lock is null, no client has been added since last, clear.
511 //the client is then the last client
512 if (client != null && lock == null)
515 //checks if the client is not already in the lists
516 // ClientHandle[] cl = clist.retrieveClientList();//lock);//clist.retrieveClientList();
517 // ask to the client to cpoy application data into the document
518 client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null, client,null);
520 // if(cl == null || cl.length<1 )
521 // {//no client has registered as active
522 //the client is the last one, so close current session
523 log.info("last client removed: closing session");
526 client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client,null);
527 log.debug("close document request done");
529 getSessionManager().removeSession(client.getSessionHandle());
530 log.debug("Session removed");
534 log.debug("not the last client found ");
535 // ask to the client to cpoy application data into the document
536 // client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null, client,null);
541 log.debug("Stopping EventGenerator..");
542 client.evgen.stopWatching();
544 watcher.setHandler(null);//Do not check if the client is the last client. watcher will shutdown anyway
545 // watcher.haltWatch();
552 * @return the sessionManager
554 protected SimpleSessionManager getSessionManager() {
555 return sessionManager;
558 * @param sessionManager the sessionManager to set
560 protected void setSessionManager(SimpleSessionManager sessionManager) {
561 this.sessionManager = sessionManager;
563 public ClientsFile getStoreDocFile() {
564 if (storedocfile==null) {
570 ClientSessionFileWatcherElement clistWatchElement=null;
571 public ClientSessionFileWatcherElement getClientWatcherElement() {
572 if (clistWatchElement==null) {
573 clistWatchElement=new ClientSessionFileWatcherElement(clist,null);
575 return clistWatchElement;
578 * writes a vector of vorba Ids to the session.
580 public void setModObjectList(Vector modObjects) {
581 log.debug("Writing "+modObjects.size()+" ids to ModObjectList");
582 // TODO Auto-generated method stub
585 * get current list of modified objects.
586 * @return null or Vector of objects
588 public Vector getModObjectList() {
589 log.debug("Reading modObjectList");