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 = 1100 ;
89 * time between checking
91 public int WATCH_SLEEP=30;
93 //protected String clientFileDirectory = "clients";
96 * called to clear update flag after a successful offline storage event
98 protected void clearUnsavedFlag() {
99 SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
100 if (!laststored.clearFlag())
101 log.warn("Unsaved flag was not cleared for "+sessionDir);
104 * called to indicate session document has been modified.
107 protected void setUnsavedFlag() {
108 SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
109 if (!laststored.setFlag())
110 log.warn("Couldn't set the Unsaved flag for "+sessionDir);
114 * @return true if session document has been modified since last offline storage event
116 protected boolean getUnsavedFlag() {
117 SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
118 return laststored.checkFlag();
123 public static final String SESSION_LOG="Log.txt";
124 private static Log log = LogFactory.getLog(VamsasSession.class);
125 protected Logger slog = Logger.getLogger("uk.ac.vamsas.client.SessionLog");
127 * setup the sessionLog using Log4j.
128 * @throws IOException
130 private void initLog() throws IOException {
131 // TODO: fix session event logging
132 // LATER: make dedicated appender format for session log.
133 /*Appender app = slog.getAppender("log4j.appender.SESSIONLOG");
134 // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
135 // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
136 for (Enumeration e = slog.getAllAppenders() ; e.hasMoreElements() ;) {
137 System.out.println(e.nextElement());
142 File sessionLogFile = new File(this.sessionDir, SESSION_LOG);
143 slog.addAppender(new FileAppender(new PatternLayout("%-4r [%t] %-5p %c %x - %m%n"), sessionLogFile.getAbsolutePath(), true));
145 log.info("No appender for SessionLog");
150 * the sessionDir is given as the session location for new clients.
152 protected File sessionDir;
154 * holds the list of attached clients
157 public static final String CLIENT_LIST="Clients.obj";
161 VamsasFile vamArchive;
162 public static final String VAMSAS_OBJ="VamDoc.jar";
165 * sets up the vamsas session files and watchers in sessionDir
168 protected VamsasSession(File sessionDir1) throws IOException {
169 if (sessionDir1==null)
170 throw new Error("Null directory for VamsasSession.");
171 if (sessionDir1.exists()) {
172 if (!sessionDir1.isDirectory() || !sessionDir1.canWrite() || !sessionDir1.canRead())
173 throw new IOException("Cannot access '"+sessionDir1+"' as a read/writable Directory.");
174 if (!checkSessionFiles(sessionDir1))
175 log.warn("checkSessionFiles() returned false. Possible client implementation error");
176 this.sessionDir = sessionDir1;
177 initSessionObjects();
178 slog.debug("Initialising additional VamsasSession instance");
179 log.debug("Attached to VamsasSession in "+sessionDir1);
182 // start from scratch
183 if (!sessionDir1.mkdir())
184 throw new IOException("Failed to make VamsasSession directory in "+sessionDir1);
185 createSessionFiles();
186 initSessionObjects();
187 slog.debug("Session directory created.");
188 log.debug("Initialised VamsasSession in "+sessionDir1);
192 * tests presence of existing sessionfiles files in dir
196 private boolean checkSessionFiles(File dir) throws IOException {
197 File c_file = new File(dir,CLIENT_LIST);
198 File v_doc = new File(dir,VAMSAS_OBJ);
199 if (c_file.exists() && v_doc.exists())
204 * create new empty files in dir
207 private void createSessionFiles() throws IOException {
208 if (sessionDir==null)
209 throw new IOException("Invalid call to createSessionFiles() with null sessionDir");
210 File c_file = new File(sessionDir,CLIENT_LIST);
211 File v_doc = new File(sessionDir,VAMSAS_OBJ);
212 if (!c_file.exists() && c_file.createNewFile())
213 log.debug("Created new ClientFile "+c_file); // don't care if this works or not
214 if (!v_doc.exists() && v_doc.createNewFile())
215 log.debug("Created new Vamsas Session Document File "+v_doc);
218 * construct SessionFile objects and watchers for each
220 private void initSessionObjects() throws IOException {
221 createSessionFiles();
222 if (clist!=null || vamArchive!=null)
223 throw new IOException("initSessionObjects called for initialised VamsasSession object.");
224 clist = new ClientsFile(new File(sessionDir,CLIENT_LIST));
225 vamArchive = new VamsasFile(new File(sessionDir,VAMSAS_OBJ));
226 storedocfile=new ClientsFile(new File(sessionDir, CLOSEANDSAVE_FILE));
230 * make a new watcher object for the clientFile
231 * @return new ClientFile watcher instance
233 public FileWatcher getClientWatcher() {
234 return new FileWatcher(clist.sessionFile);
237 * make a new watcher object for the vamsas Document
238 * @return new ClientFile watcher instance
240 public FileWatcher getDocWatcher() {
241 return new FileWatcher(vamArchive.sessionFile);
243 FileWatcher store_doc_file=null;
244 public ClientsFile storedocfile=null;
246 * make a new watcher object for the messages file
247 * @return new watcher instance
249 public FileWatcher getStoreWatcher() {
250 return new FileWatcher(new File(sessionDir,CLOSEANDSAVE_FILE));
254 * write to the StoreWatcher file to indicate that a storeDocumentRequest has been made.
255 * The local client's storeWatcher FileWatcher object is updated so the initial change is not registered.
260 public void addStoreDocumentRequest(ClientHandle client, UserHandle user) throws IOException {
261 // TODO: replace this with clientsFile mechanism
262 SessionFile sfw = new SessionFile(new File(sessionDir, CLOSEANDSAVE_FILE));
263 while (!sfw.lockFile())
264 log.debug("Trying to get lock for "+CLOSEANDSAVE_FILE);
265 RandomAccessFile sfwfile=sfw.fileLock.getRaFile();
266 sfwfile.setLength(0); // wipe out any old info.
267 // TODO: rationalise what gets written to this file (ie do we want other clients to read the id of the requestor?)
268 sfwfile.writeUTF(client.getClientUrn()+":"+user.getFullName()+"@"+user.getOrganization());
270 if (store_doc_file!=null)
271 store_doc_file.setState();
272 slog.info("FinalizeAppData request from "+user.getFullName()+" using "+client.getClientUrn()+"");
275 * create a new session with an existing vamsas Document - by copying it into the session.
278 public void setVamsasDocument(File archive) throws IOException {
279 log.debug("Transferring vamsas data from "+archive+" to session:"+vamArchive.sessionFile);
280 SessionFile xtantdoc = new SessionFile(archive);
281 vamArchive.updateFrom(null, xtantdoc);
282 // LATER: decide if session archive provenance should be updated to reflect access.
283 // TODO: soon! do a proper import objects from external file
284 log.debug("Transfer complete.");
287 * write session as a new vamsas Document (this will overwrite any existing file without warning)
289 * TODO: verify that lock should be released for vamsas document.
292 protected void writeVamsasDocument(File destarchive, Lock extlock) throws IOException {
293 log.debug("Transferring vamsas data from "+vamArchive.sessionFile+" to session:"+destarchive);
294 SessionFile newdoc = new SessionFile(destarchive);
295 if (extlock==null && !vamArchive.lockFile())
296 while (!vamArchive.lockFile())
297 log.info("Trying to get lock for "+vamArchive.sessionFile);
298 // TODO: LATER: decide if a provenance entry should be written in the exported document recording the export from the session
299 newdoc.updateFrom(extlock, vamArchive);
300 // 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).
303 log.debug("Transfer complete.");
306 * extant archive IO handler
308 VamsasArchive _va=null;
310 * Creates a VamsasArchive Vobject for accessing and updating document
311 * Note: this will lock the Vamsas Document for exclusive access to the client.
312 * @return session vamsas document
313 * @throws IOException if locks fail or vamsas document read fails.
315 protected VamsasArchive getVamsasDocument() throws IOException {
316 // check we haven't already done this once - probably should be done by caller
319 // patiently wait for a lock on the document. (from ArchiveClient.getUpdateable())
321 while (vamArchive.getLock()==null && --tries>0) {
323 log.debug("Trying to get a document lock for the "+tries+"'th time.");
326 throw new IOException("Failed to get lock for vamsas archive.");
328 VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true, vamArchive);
333 * Unlocks the vamsas archive session document after it has been closed.
334 * @throws IOException
336 protected void unlockVamsasDocument() throws IOException {
340 if (vamArchive!=null)
345 * create a uniquely named uk.ac.vamsas.client.simpleclient.ClientsFile.addClient(ClientHandle)ile in the session Directory
346 * @see java.io.File.createTempFile
347 * @param pref Prefix for name
348 * @param suff Suffix for name
349 * @return SessionFile object configured for the new file (of length zero)
350 * @throws IOException
352 protected SessionFile getTempSessionFile(String pref, String suff) throws IOException {
353 File tfile = File.createTempFile(pref,suff,sessionDir);
354 SessionFile tempFile = new SessionFile(tfile);
359 * add a IClient to the session
361 * add the client to the client list file
362 * @param client client to add to the session
364 protected void addClient(IClient client)
367 slog.error("Try to add a null client to the session ");
369 log.debug("Adding client "+client.getClientHandle().getClientUrn());
370 getClientWatcherElement().haltWatch();
371 clist.addClient(client.getClientHandle());
374 log.debug("Register Client as Active.");
376 client.createActiveClientFile();
377 } catch (IOException e) {
378 log.debug("Error during active client file creation.");
380 //tracks modification to the client list and readds client to the list
381 getClientWatcherElement().setHandler(new AddClientWatchCallBack(client));
382 getClientWatcherElement().enableWatch();
387 * Handler for the client watcher.
389 * If (the current client is not in the client list, it is added again;)
391 private class AddClientWatchCallBack implements WatcherCallBack
394 private IClient client ;
397 *Inits the handler with the client to check in the list
398 * @param client client to monitor in the client list
400 protected AddClientWatchCallBack (IClient client)
402 this.client = client;
406 * If the client list is modified, checks if the current is still in the list. otherwise, readds ti.
407 * @return true to enable watcher, or false to disable it in future WatcherThread cycles.
409 public boolean handleWatchEvent(WatcherElement watcher, Lock lock)
411 boolean isWatchEnable = watcher.isWatchEnabled();
412 if (lock== null)//no update on the list
413 return isWatchEnable;
414 // log.debug("change on the client list ");
419 //checks if the client is not already in the lists
420 ClientHandle[] cl = clist.retrieveClientList(lock);//clist.retrieveClientList();
421 boolean found = false;
424 for (int chi = cl.length-1; !found && chi > -1; chi--) {
425 found = cl[chi].equals(this.client.getClientHandle());
430 {log.debug("client not in the list ");
431 if( log.isDebugEnabled())
432 log.debug("the client has not been found in the list. Adding it again :"+cl);
436 log.debug("client is in the list");
439 //log.debug("isWatchEnable "+isWatchEnable);
440 return isWatchEnable;
446 * removes a client from the current session
447 * removes the client from the session client list
448 * if the client is the last one from the session (ClientList), the current session is removed
449 * from active session list.
451 * The active should add them self to the client list. To insure to close the session,when the current client is the lact active client,
452 * clears the list of clients and when two cycles to insure there is no more active client, that otherwise would have readd themself to the list
454 * @param client client to remove
456 protected void removeClient(SimpleClient client)//IClient client)
460 log.error("Null client passed to removeClient");
463 ClientSessionFileWatcherElement cwe=getClientWatcherElement();
464 if (cwe!=null && cwe.isWatchEnabled()) {
467 //set handler to check is the the last active client of the session
468 //Wait for several watchers cycle to see if the current client was the last client active in the session.
469 //if yes, close the session
471 getClientWatcherElement().setHandler(new RemoveClientWatchCallBack (client));
472 getClientWatcherElement().setTimeoutBeforeLastCycle(this.watchCycleCountBeforeLastClient);
473 log.info("remove client from list");
475 log.info("client list cleared");
481 log.debug("Releasing active client file");
482 client.releaseActiveClientFile();
483 } catch (IOException e) {
484 log.error("error during active file client release");
490 /*clist.removeClient(client.getClientHandle(),null);
491 if (this.clist.retrieveClientList() == null|| this.clist.retrieveClientList().length<1)
492 {//assume it is the last client has been removed shutting down session
493 slog.info("last client removed: removing session");
494 log.debug("last client removed: removing session");
495 this.getSessionManager().removeSession(client.getSessionHandle());
499 int active=clist.retrieveClientList().length;
500 log.debug("Still "+active+" active clients");
501 slog.info("Still "+active+" active clients");
507 * Handler for the client watcher. after a client have been removed
509 * Checks if the client is not the last active one.
511 * If (the current client is not in the client list readd it;)
513 private class RemoveClientWatchCallBack implements WatcherCallBack
516 private SimpleClient client ;
517 private boolean manualCheckOfClientCount = false;
519 *Inits the handler with the client to check in the list
520 * @param client client to monitor in the client list
522 protected RemoveClientWatchCallBack (SimpleClient client)
524 this.client = client;
528 * If the client list is modified, checks if the current is still in the list. otherwise, readds ti.
529 * @return true to enable watcher, or false to disable it in future WatcherThread cycles.
531 public boolean handleWatchEvent(WatcherElement watcher, Lock lock)
533 // if lock is null, no client has been added since last, clear.
534 //the client is then the last client
541 //checks if the client is not already in the lists
542 // ClientHandle[] cl = clist.retrieveClientList();//lock);//clist.retrieveClientList();
543 // ask to the client to cpoy application data into the document
544 client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null, client,null);
545 boolean islastClient = true;
546 if (manualCheckOfClientCount)
548 log.debug("manual checking of count of client");
549 //checks if the client is not already in the lists
550 ClientHandle[] cl = clist.retrieveClientList();//lock);//clist.retrieveClientList();
551 if(cl == null || cl.length<1 )
552 // {//no client has registered as active
555 log.debug("list is empty");
558 islastClient = false;
559 log.debug("list is not empty");
561 // if(cl == null || cl.length<1 )
562 // {//no client has registered as active
565 //the client is the last one, so close current session
566 log.info("last client removed: closing session");
569 client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client,null);
570 log.debug("close document request done");
572 getSessionManager().removeSession(client.getSessionHandle());
573 log.debug("Session removed");
578 log.debug("not the last client found ");
579 // ask to the client to cpoy application data into the document
580 // client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null, client,null);
585 log.debug("Stopping EventGenerator..");
586 client.evgen.stopWatching();
588 watcher.setHandler(null);//Do not check if the client is the last client. watcher will shutdown anyway
589 // watcher.haltWatch();
596 * @return the sessionManager
598 protected SimpleSessionManager getSessionManager() {
599 return sessionManager;
602 * @param sessionManager the sessionManager to set
604 protected void setSessionManager(SimpleSessionManager sessionManager) {
605 this.sessionManager = sessionManager;
607 public ClientsFile getStoreDocFile() {
608 if (storedocfile==null) {
614 ClientSessionFileWatcherElement clistWatchElement=null;
615 public ClientSessionFileWatcherElement getClientWatcherElement() {
616 if (clistWatchElement==null) {
617 clistWatchElement=new ClientSessionFileWatcherElement(clist,null);
619 return clistWatchElement;
622 * writes a vector of vorba Ids to the session.
624 public void setModObjectList(Vector modObjects) {
625 log.debug("Writing "+modObjects.size()+" ids to ModObjectList");
626 // TODO Auto-generated method stub
629 * get current list of modified objects.
630 * @return null or Vector of objects
632 public Vector getModObjectList() {
633 log.debug("Reading modObjectList");