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;
10 import java.util.Vector;
12 import org.apache.commons.logging.Log;
13 import org.apache.commons.logging.LogFactory;
14 import org.apache.log4j.Appender;
15 import org.apache.log4j.Logger;
16 import org.apache.log4j.FileAppender;
17 import org.apache.log4j.PatternLayout;
19 import uk.ac.vamsas.client.ClientHandle;
20 import uk.ac.vamsas.client.IClient;
21 import uk.ac.vamsas.client.UserHandle;
23 * Does all the IO operations for a SimpleClient instance accessing
24 * a SimpleClient vamsas session.
26 * Basically, it defines the various standard names for the files
27 * in the session directory (that maps to the sessionUrn),
28 * provides constructors for the file handlers and watchers of
29 * those file entities, and some higher level methods
30 * to check and change the state flags for the session.
32 * TODO: move the stuff below to the SimpleClientFactory documentation.
33 * much may not be valid now :
34 * Vamsas client is intialised with a path to create live session directories.
35 * This path may contain a vamsas.properties file
36 * that sets additional parameters (otherwise client
37 * just uses the one on the classpath).
39 * A vamsas session consists of :
40 * SessionDir - translates to urn of a live session.
41 * Contains: Vamsas Document (as a jar), Session client list file,
42 * both of which may be locked, and additional
43 * temporary versions of these files when write
44 * operations are taking place.
47 * - vamsasdocument.xml : core info
49 * - <applicationname>.version.sessionnumber.raw (string given in vamsasdocument.xml applicationData entry)
52 * - filename given in the vamsasdocument.xml. Should be checked for validity by any client and rewritten if necessary.
53 * The lockfile can point to the jar itself.
55 * Initially - documentHandler either:
56 * - creates a zip for a new session for the client
57 * - connect to an existing session zip
58 * 1. reads session urn file
60 * 3. examines session - decide whether to create new application data slice or connect to one stored in session.
61 * 4. writes info into session file
62 * 5. releases lock and generates local client events.
63 * 6. Creates Watcher thread to generate events.
66 * - Update watcher checks for file change -
68 * Procedures for file based session message exchange
69 * - session document modification flag
73 public class VamsasSession {
75 * indicator file for informing other processes that
76 * they should finalise their vamsas datasets for
77 * storing into a vamsas archive.
79 public static final String CLOSEANDSAVE_FILE="stored.log";
81 * session file storing the last_stored_stat data
83 public static final String MODIFIEDDOC_FILE="modified";
86 private SimpleSessionManager sessionManager = null;
89 * called to clear update flag after a successful offline storage event
91 protected void clearUnsavedFlag() {
92 SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
93 if (!laststored.clearFlag())
94 log.warn("Unsaved flag was not cleared for "+sessionDir);
97 * called to indicate session document has been modified.
100 protected void setUnsavedFlag() {
101 SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
102 if (!laststored.setFlag())
103 log.warn("Couldn't set the Unsaved flag for "+sessionDir);
107 * @return true if session document has been modified since last offline storage event
109 protected boolean getUnsavedFlag() {
110 SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
111 return laststored.checkFlag();
116 public static final String SESSION_LOG="Log.txt";
117 private static Log log = LogFactory.getLog(VamsasSession.class);
118 protected Logger slog = Logger.getLogger("uk.ac.vamsas.client.SessionLog");
120 * setup the sessionLog using Log4j.
121 * @throws IOException
123 private void initLog() throws IOException {
124 // TODO: fix session event logging
125 // LATER: make dedicated appender format for session log.
126 /*Appender app = slog.getAppender("log4j.appender.SESSIONLOG");
127 // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
128 // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
129 for (Enumeration e = slog.getAllAppenders() ; e.hasMoreElements() ;) {
130 System.out.println(e.nextElement());
135 File sessionLogFile = new File(this.sessionDir, SESSION_LOG);
136 slog.addAppender(new FileAppender(new PatternLayout("%-4r [%t] %-5p %c %x - %m%n"), sessionLogFile.getAbsolutePath(), true));
138 log.info("No appender for SessionLog");
143 * the sessionDir is given as the session location for new clients.
145 protected File sessionDir;
147 * holds the list of attached clients
150 public static final String CLIENT_LIST="Clients.obj";
154 VamsasFile vamArchive;
155 public static final String VAMSAS_OBJ="VamDoc.jar";
158 * sets up the vamsas session files and watchers in sessionDir
161 protected VamsasSession(File sessionDir1) throws IOException {
162 if (sessionDir1==null)
163 throw new Error("Null directory for VamsasSession.");
164 if (sessionDir1.exists()) {
165 if (!sessionDir1.isDirectory() || !sessionDir1.canWrite() || !sessionDir1.canRead())
166 throw new IOException("Cannot access '"+sessionDir1+"' as a read/writable Directory.");
167 if (!checkSessionFiles(sessionDir1))
168 log.warn("checkSessionFiles() returned false. Possible client implementation error");
169 this.sessionDir = sessionDir1;
170 initSessionObjects();
171 slog.debug("Initialising additional VamsasSession instance");
172 log.debug("Attached to VamsasSession in "+sessionDir1);
175 // start from scratch
176 if (!sessionDir1.mkdir())
177 throw new IOException("Failed to make VamsasSession directory in "+sessionDir1);
178 createSessionFiles();
179 initSessionObjects();
180 slog.debug("Session directory created.");
181 log.debug("Initialised VamsasSession in "+sessionDir1);
185 * tests presence of existing sessionfiles files in dir
189 private boolean checkSessionFiles(File dir) throws IOException {
190 File c_file = new File(dir,CLIENT_LIST);
191 File v_doc = new File(dir,VAMSAS_OBJ);
192 if (c_file.exists() && v_doc.exists())
197 * create new empty files in dir
200 private void createSessionFiles() throws IOException {
201 if (sessionDir==null)
202 throw new IOException("Invalid call to createSessionFiles() with null sessionDir");
203 File c_file = new File(sessionDir,CLIENT_LIST);
204 File v_doc = new File(sessionDir,VAMSAS_OBJ);
205 if (!c_file.exists() && c_file.createNewFile())
206 log.debug("Created new ClientFile "+c_file); // don't care if this works or not
207 if (!v_doc.exists() && v_doc.createNewFile())
208 log.debug("Created new Vamsas Session Document File "+v_doc);
211 * construct SessionFile objects and watchers for each
213 private void initSessionObjects() throws IOException {
214 createSessionFiles();
215 if (clist!=null || vamArchive!=null)
216 throw new IOException("initSessionObjects called for initialised VamsasSession object.");
217 clist = new ClientsFile(new File(sessionDir,CLIENT_LIST));
218 vamArchive = new VamsasFile(new File(sessionDir,VAMSAS_OBJ));
219 storedocfile=new ClientsFile(new File(sessionDir, CLOSEANDSAVE_FILE));
223 * make a new watcher object for the clientFile
224 * @return new ClientFile watcher instance
226 public FileWatcher getClientWatcher() {
227 return new FileWatcher(clist.sessionFile);
230 * make a new watcher object for the vamsas Document
231 * @return new ClientFile watcher instance
233 public FileWatcher getDocWatcher() {
234 return new FileWatcher(vamArchive.sessionFile);
236 FileWatcher store_doc_file=null;
237 public ClientsFile storedocfile=null;
239 * make a new watcher object for the messages file
240 * @return new watcher instance
242 public FileWatcher getStoreWatcher() {
243 return new FileWatcher(new File(sessionDir,CLOSEANDSAVE_FILE));
247 * write to the StoreWatcher file to indicate that a storeDocumentRequest has been made.
248 * The local client's storeWatcher FileWatcher object is updated so the initial change is not registered.
253 public void addStoreDocumentRequest(ClientHandle client, UserHandle user) throws IOException {
254 // TODO: replace this with clientsFile mechanism
255 SessionFile sfw = new SessionFile(new File(sessionDir, CLOSEANDSAVE_FILE));
256 while (!sfw.lockFile())
257 log.debug("Trying to get lock for "+CLOSEANDSAVE_FILE);
258 RandomAccessFile sfwfile=sfw.fileLock.getRaFile();
259 sfwfile.setLength(0); // wipe out any old info.
260 // TODO: rationalise what gets written to this file (ie do we want other clients to read the id of the requestor?)
261 sfwfile.writeUTF(client.getClientUrn()+":"+user.getFullName()+"@"+user.getOrganization());
263 if (store_doc_file!=null)
264 store_doc_file.setState();
265 slog.info("FinalizeAppData request from "+user.getFullName()+" using "+client.getClientUrn()+"");
268 * create a new session with an existing vamsas Document - by copying it into the session.
271 public void setVamsasDocument(File archive) throws IOException {
272 log.debug("Transferring vamsas data from "+archive+" to session:"+vamArchive.sessionFile);
273 SessionFile xtantdoc = new SessionFile(archive);
274 vamArchive.updateFrom(null, xtantdoc);
275 // LATER: decide if session archive provenance should be updated to reflect access.
276 // TODO: soon! do a proper import objects from external file
277 log.debug("Transfer complete.");
280 * write session as a new vamsas Document (this will overwrite any existing file without warning)
282 * TODO: verify that lock should be released for vamsas document.
285 protected void writeVamsasDocument(File destarchive, Lock extlock) throws IOException {
286 log.debug("Transferring vamsas data from "+vamArchive.sessionFile+" to session:"+destarchive);
287 SessionFile newdoc = new SessionFile(destarchive);
288 if (extlock==null && !vamArchive.lockFile())
289 while (!vamArchive.lockFile())
290 log.info("Trying to get lock for "+vamArchive.sessionFile);
291 // TODO: LATER: decide if a provenance entry should be written in the exported document recording the export from the session
292 newdoc.updateFrom(extlock, vamArchive);
293 // 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).
296 log.debug("Transfer complete.");
299 * extant archive IO handler
301 VamsasArchive _va=null;
303 * Creates a VamsasArchive Vobject for accessing and updating document
304 * Note: this will lock the Vamsas Document for exclusive access to the client.
305 * @return session vamsas document
306 * @throws IOException if locks fail or vamsas document read fails.
308 protected VamsasArchive getVamsasDocument() throws IOException {
309 // check we haven't already done this once - probably should be done by caller
312 // patiently wait for a lock on the document. (from ArchiveClient.getUpdateable())
314 while (vamArchive.getLock()==null && --tries>0) {
316 log.debug("Trying to get a document lock for the "+tries+"'th time.");
319 throw new IOException("Failed to get lock for vamsas archive.");
321 VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true, vamArchive);
326 * Unlocks the vamsas archive session document after it has been closed.
327 * @throws IOException
329 protected void unlockVamsasDocument() throws IOException {
333 if (vamArchive!=null)
338 * create a uniquely named uk.ac.vamsas.client.simpleclient.ClientsFile.addClient(ClientHandle)ile in the session Directory
339 * @see java.io.File.createTempFile
340 * @param pref Prefix for name
341 * @param suff Suffix for name
342 * @return SessionFile object configured for the new file (of length zero)
343 * @throws IOException
345 protected SessionFile getTempSessionFile(String pref, String suff) throws IOException {
346 File tfile = File.createTempFile(pref,suff,sessionDir);
347 SessionFile tempFile = new SessionFile(tfile);
352 * add a IClient to the session
354 * add the client to the client list file
355 * @param client client to add to the session
357 protected void addClient(IClient client)
360 slog.error("Try to add a null client to the session ");
362 log.debug("Adding client "+client.getClientHandle().getClientUrn());
363 getClientWatcherElement().haltWatch();
364 clist.addClient(client.getClientHandle());
365 getClientWatcherElement().enableWatch();
372 * removes a client from the current session
373 * removes the client from the session client list
374 * if the client is the last one from the session (ClientList), the current session is removed
375 * from active session list.
377 * @param client client to remove
379 protected void removeClient(IClient client)
383 log.error("Null client passed to removeClient");
386 SessionFileWatcherElement cwe=getClientWatcherElement();
387 if (cwe!=null && cwe.isWatchEnabled()) {
390 clist.removeClient(client.getClientHandle(),null);
391 if (this.clist.retrieveClientList() == null|| this.clist.retrieveClientList().length<1)
392 {//assume it is the last client has been removed shutting down session
393 slog.info("last client removed: removing session");
394 log.debug("last client removed: removing session");
395 this.getSessionManager().removeSession(client.getSessionHandle());
399 int active=clist.retrieveClientList().length;
400 log.debug("Still "+active+" active clients");
401 slog.info("Still "+active+" active clients");
408 * @return the sessionManager
410 protected SimpleSessionManager getSessionManager() {
411 return sessionManager;
414 * @param sessionManager the sessionManager to set
416 protected void setSessionManager(SimpleSessionManager sessionManager) {
417 this.sessionManager = sessionManager;
419 public ClientsFile getStoreDocFile() {
420 if (storedocfile==null) {
425 SessionFileWatcherElement clistWatchElement=null;
426 public SessionFileWatcherElement getClientWatcherElement() {
427 if (clistWatchElement==null) {
428 clistWatchElement=new SessionFileWatcherElement(clist,null);
430 return clistWatchElement;
433 * writes a vector of vorba Ids to the session.
435 public void setModObjectList(Vector modObjects) {
436 log.debug("Writing "+modObjects.size()+" ids to ModObjectList");
437 // TODO Auto-generated method stub
440 * get current list of modified objects.
441 * @return null or Vector of objects
443 public Vector getModObjectList() {
444 log.debug("Reading modObjectList");