1 package uk.ac.vamsas.client.simpleclient;
4 import java.io.IOException;
5 import java.io.PrintStream;
6 import java.io.PrintWriter;
7 import java.io.RandomAccessFile;
9 import java.util.Enumeration;
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.apache.log4j.PatternLayout;
18 import uk.ac.vamsas.client.ClientHandle;
19 import uk.ac.vamsas.client.IClient;
20 import uk.ac.vamsas.client.UserHandle;
22 * Does all the IO operations for a SimpleClient instance accessing
23 * a SimpleClient vamsas session.
25 * Basically, it defines the various standard names for the files
26 * in the session directory (that maps to the sessionUrn),
27 * provides constructors for the file handlers and watchers of
28 * those file entities, and some higher level methods
29 * to check and change the state flags for the session.
31 * TODO: move the stuff below to the SimpleClientFactory documentation.
32 * much may not be valid now :
33 * Vamsas client is intialised with a path to create live session directories.
34 * This path may contain a vamsas.properties file
35 * that sets additional parameters (otherwise client
36 * just uses the one on the classpath).
38 * A vamsas session consists of :
39 * SessionDir - translates to urn of a live session.
40 * Contains: Vamsas Document (as a jar), Session client list file,
41 * both of which may be locked, and additional
42 * temporary versions of these files when write
43 * operations are taking place.
46 * - vamsasdocument.xml : core info
48 * - <applicationname>.version.sessionnumber.raw (string given in vamsasdocument.xml applicationData entry)
51 * - filename given in the vamsasdocument.xml. Should be checked for validity by any client and rewritten if necessary.
52 * The lockfile can point to the jar itself.
54 * Initially - documentHandler either:
55 * - creates a zip for a new session for the client
56 * - connect to an existing session zip
57 * 1. reads session urn file
59 * 3. examines session - decide whether to create new application data slice or connect to one stored in session.
60 * 4. writes info into session file
61 * 5. releases lock and generates local client events.
62 * 6. Creates Watcher thread to generate events.
65 * - Update watcher checks for file change -
67 * Procedures for file based session message exchange
68 * - session document modification flag
72 public class VamsasSession {
74 * indicator file for informing other processes that
75 * they should finalise their vamsas datasets for
76 * storing into a vamsas archive.
78 public static final String CLOSEANDSAVE_FILE="stored.log";
80 * session file storing the last_stored_stat data
82 public static final String MODIFIEDDOC_FILE="modified";
85 private SimpleSessionManager sessionManager = null;
88 * called to clear update flag after a successful offline storage event
90 protected void clearUnsavedFlag() {
91 SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
92 if (!laststored.clearFlag())
93 log.warn("Unsaved flag was not cleared for "+sessionDir);
96 * called to indicate session document has been modified.
99 protected void setUnsavedFlag() {
100 SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
101 if (!laststored.setFlag())
102 log.warn("Couldn't set the Unsaved flag for "+sessionDir);
106 * @return true if session document has been modified since last offline storage event
108 protected boolean getUnsavedFlag() {
109 SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
110 return laststored.checkFlag();
115 public static final String SESSION_LOG="Log.txt";
116 private static Log log = LogFactory.getLog(VamsasSession.class);
117 protected Logger slog = Logger.getLogger("uk.ac.vamsas.client.SessionLog");
119 * setup the sessionLog using Log4j.
120 * @throws IOException
122 private void initLog() throws IOException {
123 // TODO: fix session event logging
124 // LATER: make dedicated appender format for session log.
125 /*Appender app = slog.getAppender("log4j.appender.SESSIONLOG");
126 // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
127 // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
128 for (Enumeration e = slog.getAllAppenders() ; e.hasMoreElements() ;) {
129 System.out.println(e.nextElement());
134 File sessionLogFile = new File(this.sessionDir, SESSION_LOG);
135 slog.addAppender(new FileAppender(new PatternLayout("%-4r [%t] %-5p %c %x - %m%n"), sessionLogFile.getAbsolutePath(), true));
137 log.info("No appender for SessionLog");
142 * the sessionDir is given as the session location for new clients.
144 protected File sessionDir;
146 * holds the list of attached clients
149 public static final String CLIENT_LIST="Clients.obj";
153 VamsasFile vamArchive;
154 public static final String VAMSAS_OBJ="VamDoc.jar";
157 * sets up the vamsas session files and watchers in sessionDir
160 protected VamsasSession(File sessionDir1) throws IOException {
161 if (sessionDir1==null)
162 throw new Error("Null directory for VamsasSession.");
163 if (sessionDir1.exists()) {
164 if (!sessionDir1.isDirectory() || !sessionDir1.canWrite() || !sessionDir1.canRead())
165 throw new IOException("Cannot access '"+sessionDir1+"' as a read/writable Directory.");
166 if (!checkSessionFiles(sessionDir1))
167 log.warn("checkSessionFiles() returned false. Possible client implementation error");
168 this.sessionDir = sessionDir1;
169 initSessionObjects();
170 slog.debug("Initialising additional VamsasSession instance");
171 log.debug("Attached to VamsasSession in "+sessionDir1);
174 // start from scratch
175 if (!sessionDir1.mkdir())
176 throw new IOException("Failed to make VamsasSession directory in "+sessionDir1);
177 createSessionFiles();
178 initSessionObjects();
179 slog.debug("Session directory created.");
180 log.debug("Initialised VamsasSession in "+sessionDir1);
184 * tests presence of existing sessionfiles files in dir
188 private boolean checkSessionFiles(File dir) throws IOException {
189 File c_file = new File(dir,CLIENT_LIST);
190 File v_doc = new File(dir,VAMSAS_OBJ);
191 if (c_file.exists() && v_doc.exists())
196 * create new empty files in dir
199 private void createSessionFiles() throws IOException {
200 if (sessionDir==null)
201 throw new IOException("Invalid call to createSessionFiles() with null sessionDir");
202 File c_file = new File(sessionDir,CLIENT_LIST);
203 File v_doc = new File(sessionDir,VAMSAS_OBJ);
204 if (!c_file.exists() && c_file.createNewFile())
205 log.debug("Created new ClientFile "+c_file); // don't care if this works or not
206 if (!v_doc.exists() && v_doc.createNewFile())
207 log.debug("Created new Vamsas Session Document File "+v_doc);
210 * construct SessionFile objects and watchers for each
212 private void initSessionObjects() throws IOException {
213 createSessionFiles();
214 if (clist!=null || vamArchive!=null)
215 throw new IOException("initSessionObjects called for initialised VamsasSession object.");
216 clist = new ClientsFile(new File(sessionDir,CLIENT_LIST));
217 vamArchive = new VamsasFile(new File(sessionDir,VAMSAS_OBJ));
218 storedocfile=new ClientsFile(new File(sessionDir, CLOSEANDSAVE_FILE));
222 * make a new watcher object for the clientFile
223 * @return new ClientFile watcher instance
225 public FileWatcher getClientWatcher() {
226 return new FileWatcher(clist.sessionFile);
229 * make a new watcher object for the vamsas Document
230 * @return new ClientFile watcher instance
232 public FileWatcher getDocWatcher() {
233 return new FileWatcher(vamArchive.sessionFile);
235 FileWatcher store_doc_file=null;
236 public ClientsFile storedocfile=null;
238 * make a new watcher object for the messages file
239 * @return new watcher instance
241 public FileWatcher getStoreWatcher() {
242 return new FileWatcher(new File(sessionDir,CLOSEANDSAVE_FILE));
246 * write to the StoreWatcher file to indicate that a storeDocumentRequest has been made.
247 * The local client's storeWatcher FileWatcher object is updated so the initial change is not registered.
252 public void addStoreDocumentRequest(ClientHandle client, UserHandle user) throws IOException {
253 // TODO: replace this with clientsFile mechanism
254 SessionFile sfw = new SessionFile(new File(sessionDir, CLOSEANDSAVE_FILE));
255 while (!sfw.lockFile())
256 log.debug("Trying to get lock for "+CLOSEANDSAVE_FILE);
257 RandomAccessFile sfwfile=sfw.fileLock.getRaFile();
258 sfwfile.setLength(0); // wipe out any old info.
259 // TODO: rationalise what gets written to this file (ie do we want other clients to read the id of the requestor?)
260 sfwfile.writeUTF(client.getClientUrn()+":"+user.getFullName()+"@"+user.getOrganization());
262 if (store_doc_file!=null)
263 store_doc_file.setState();
264 slog.info("FinalizeAppData request from "+user.getFullName()+" using "+client.getClientUrn()+"");
267 * create a new session with an existing vamsas Document - by copying it into the session.
270 public void setVamsasDocument(File archive) throws IOException {
271 log.debug("Transferring vamsas data from "+archive+" to session:"+vamArchive.sessionFile);
272 SessionFile xtantdoc = new SessionFile(archive);
273 vamArchive.updateFrom(null, xtantdoc);
274 // LATER: decide if session archive provenance should be updated to reflect access.
275 // TODO: soon! do a proper import objects from external file
276 log.debug("Transfer complete.");
279 * write session as a new vamsas Document (this will overwrite any existing file without warning)
281 * TODO: verify that lock should be released for vamsas document.
284 protected void writeVamsasDocument(File destarchive, Lock extlock) throws IOException {
285 log.debug("Transferring vamsas data from "+vamArchive.sessionFile+" to session:"+destarchive);
286 SessionFile newdoc = new SessionFile(destarchive);
287 if (extlock==null && !vamArchive.lockFile())
288 while (!vamArchive.lockFile())
289 log.info("Trying to get lock for "+vamArchive.sessionFile);
290 // TODO: LATER: decide if a provenance entry should be written in the exported document recording the export from the session
291 newdoc.updateFrom(extlock, vamArchive);
292 // 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).
295 log.debug("Transfer complete.");
299 * Creates a VamsasArchive Vobject for accessing and updating document
300 * Note: this will lock the Vamsas Document for exclusive access to the client.
301 * @return session vamsas document
302 * @throws IOException if locks fail or vamsas document read fails.
304 protected VamsasArchive getVamsasDocument() throws IOException {
305 // TODO: check we haven't already done this once - probably should be done by caller
306 // patiently wait for a lock on the document. (from ArchiveClient.getUpdateable())
308 while (vamArchive.getLock()==null && --tries>0) {
310 log.debug("Trying to get a document lock for the "+tries+"'th time.");
313 throw new IOException("Failed to get lock for vamsas archive.");
315 VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true, vamArchive);
320 * Unlocks the vamsas archive session document after it has been closed.
321 * @throws IOException
323 protected void unlockVamsasDocument() throws IOException {
324 if (vamArchive!=null)
328 * create a uniquely named uk.ac.vamsas.client.simpleclient.ClientsFile.addClient(ClientHandle)ile in the session Directory
329 * @see java.io.File.createTempFile
330 * @param pref Prefix for name
331 * @param suff Suffix for name
332 * @return SessionFile object configured for the new file (of length zero)
333 * @throws IOException
335 protected SessionFile getTempSessionFile(String pref, String suff) throws IOException {
336 File tfile = File.createTempFile(pref,suff,sessionDir);
337 SessionFile tempFile = new SessionFile(tfile);
342 * add a IClient to the session
344 * add the client to the client list file
345 * @param client client to add to the session
347 protected void addClient(IClient client)
350 slog.error("Try to add a null client to the session ");
352 log.debug("Adding client "+client.getClientHandle().getClientUrn());
353 getClientWatcherElement().haltWatch();
354 clist.addClient(client.getClientHandle());
355 getClientWatcherElement().enableWatch();
362 * removes a client from the current session
363 * removes the client from the session client list
364 * if the client is the last one from the session (ClientList), the current session is removed
365 * from active session list.
367 * @param client client to remove
369 protected void removeClient(IClient client)
373 log.error("Null client passed to removeClient");
376 SessionFileWatcherElement cwe=getClientWatcherElement();
377 if (cwe!=null && cwe.isWatchEnabled()) {
382 clist.removeClient(client.getClientHandle(),null);
383 if (this.clist.retrieveClientList() == null|| this.clist.retrieveClientList().length<1)
384 {//assume it is the last client has been removed shutting down session
385 slog.info("last client removed: removing session");
386 log.debug("last client removed: removing session");
387 this.getSessionManager().removeSession(client.getSessionHandle());
391 int active=clist.retrieveClientList().length;
392 log.debug("Still "+active+" active clients");
393 slog.info("Still "+active+" active clients");
400 * @return the sessionManager
402 protected SimpleSessionManager getSessionManager() {
403 return sessionManager;
406 * @param sessionManager the sessionManager to set
408 protected void setSessionManager(SimpleSessionManager sessionManager) {
409 this.sessionManager = sessionManager;
411 public ClientsFile getStoreDocFile() {
412 if (storedocfile==null) {
417 SessionFileWatcherElement clistWatchElement=null;
418 public SessionFileWatcherElement getClientWatcherElement() {
419 if (clistWatchElement==null) {
420 clistWatchElement=new SessionFileWatcherElement(clist,null);
422 return clistWatchElement;