package org.vamsas.client;
import java.beans.PropertyChangeListener;
+import java.io.IOException;
/**
* Defines the methods availabable to a vamsas
* get vamsas document with
* user and app specific data
* IClientDocuments are not thread-safe.
+ * TODO: New exception for failed document lock.
+ * @throws IOException if lock is not obtainable for the document in the session
*/
- public IClientDocument getClientDocument();
+ public IClientDocument getClientDocument() throws IOException;
/**
* Queue new Vorba objects for storage and propagation
* to other clients (via Event.DOCUMENT_UPDATE based
private SimpleClient client;
private Hashtable handlers; // manager object
private VamsasSession session;
-
+
+ /**
+ * list with all the clientHandles for the session
+ */
protected FileWatcher clientfile=null;
+ /**
+ * the session's vamsasDocument
+ */
protected FileWatcher vamsasfile=null;
+ /**
+ * written to by client when its app calls storeDocument.
+ */
protected FileWatcher storeFile=null;
private boolean watch=false;
}
if ((watchlock=clientfile.getChangedState())!=null) {
// see what happened to the clientfile - compare our internal version with the one in the file, or just send the updated list out...?
- raised++;
+ //
+ /**
+ * Generated when a new vamsas client is attached to a session (Handle is
+ * passed) Note: the newly created client does not receive the event.
+ *
+ public static final String CLIENT_CREATION = "org.vamsas.client.events.clientCreateEvent";
+ */ // as the test
+ /**
+ * Generated when a vamsas client leaves a session (Handle is passed to all
+ * others).
+ public static final String CLIENT_FINALIZATION = "org.vamsas.client.events.clientFinalizationEvent";
+ */ // again - as the test.
+ raised++;
}
if ((watchlock=vamsasfile.getChangedState())!=null) {
- // pass IClientDocument instance to app handler ?
+
+ /**
+ * Generated when a client has finished updating the document. Passes
+ * applicationHandle of client so the updating client can recognise its own
+ * updates.
+ public static final String DOCUMENT_UPDATE = "org.vamsas.client.events.documentUpdateEvent";
+ */
+ // read apphandle from 'lastUpdate' session file.
+ // pass apphandle name to appHandler ?
+
}
+ /**
+ * Generated when a new vamsas document is created (perhaps from some existing
+ * Vamsas data) so an application may do its own data space initialization.
+ * TODO: decide if this is called when an app is connected to a stored
+ * session...
+ public static final String DOCUMENT_CREATE = "org.vamsas.client.events.documentCreateEvent";
+ */
+ // check if this session's appInit flag is set - if not - generate event for this app.
+ // prolly don't need this at the moment - when an app does getDocument it can to the initing then.
+
+
+ /**
+ * Generated prior to session Shutdown, after the last participating vamsas
+ * client has finalized.
+ * TODO: decide on purpose of this ? is this for benefit of multi-session Apps only ?
+ public static final String SESSION_SHUTDOWN = "org.vamsas.client.events.SessionShutdownEvent";
+ */
+
+ /**
+ * Generated for all clients when any client calls IClient.storeDocument() to
+ * allow them to store any updates before an offline copy of the session is
+ * created. Any client that handles this should call the
+ * IClient.getDocument(), update and then IClient.updateDocument in the same
+ * handler thread.
+ * EventName: <Vamsas-session URN>
+ * NewValue: org.vamsas.client.IClient for session.
+ *
+ public static final String DOCUMENT_FINALIZEAPPDATA = "org.vamsas.client.events.DocumentFinalizeAppData";
+*/
+ // watch for finalization semaphore (last finalised sessionFile).
+
+ /**
+ * Generated by Vorba stub after the penultimate client makes a call to
+ * closeDocument(). Sequence is as follows : 1. All other vamsas clients have
+ * called closeDocument() 2. Final living client monitors closures, and
+ * realises that it is last. 3. Final client generates event to prompt
+ * associated application to inquire if the user wishes to save the document
+ * for future reference.
+ * * Any call to closeDocument in a thread other than the registered
+ * EventListener will block until the RequestToClose handler has exited.
+ *
+ */
+ // public static final String DOCUMENT_REQUESTTOCLOSE = "org.vamas.client.DocumentRequestToCloseEvent";
+
return raised;
}
private void initEvents() {
}
+ /**
+ * Events raised by IClient and propagated to others in session
+ */
+
+ /**
+ * number of milliseconds between any file state check.
+ */
+ long POLL_UNIT = 20;
+ protected void wait(int u) {
+ if (u<=0)
+ u=1;
+ long l = System.currentTimeMillis()+POLL_UNIT*u;
+ while (System.currentTimeMillis()<l)
+ ;
+ }
+
+
+
+ int STORE_WAIT=5; // how many units before we decide all clients have finalized their appdatas
+
+ /**
+ * client App requests offline storage of vamsas data.
+ * Call blocks whilst other apps do any appData finalizing
+ * and then returns (after locking the vamsasDocument in the session)
+ * @return Lock for session.vamArchive
+ */
+ protected Lock want_to_store() {
+ log.debug("Waiting for other apps to do FinalizeApp handling.");
+ try {
+ session.addStoreDocumentRequest(client.getClientHandle(), client.getUserHandle());
+ } catch (Exception e) {
+ log.warn("Whilst writing StoreDocumentRequest for ("+client.getClientHandle().getClientUrn()+" "+client.getUserHandle(),
+ e);
+ log.info("trying to continue.");
+ }
+
+ int units = 0;
+ while (units<STORE_WAIT) {
+ wait(1);
+ if (storeFile.hasChanged() || vamsasfile.hasChanged())
+ units=0;
+ else
+ units++;
+ }
+ log.debug("finished waiting.");
+ return session.vamArchive.getLock();
+ }
/**
* probably don't need any of these below.
TYPES.put(SIMPLECLIENT, SessionUrn.class);
TYPES.put(SessionUrn.VAMSASDOCUMENT, SessionUrn.class);
}
-
+
public SessionUrn(File sessionLocation) {
+ // TODO: LATER: implement switch to have vamsas document or simpleclient sessions for same constructor
super(SIMPLECLIENT, sessionLocation);
+ //else
+ // super(VAMSASDOCUMENT, sessionLocation);
+ }
+ public SessionUrn(VamsasSession session) {
+ super(SIMPLECLIENT, session.sessionDir);
}
}
import java.beans.PropertyChangeSupport;
import java.io.BufferedReader;
import java.io.File;
+import java.io.IOException;
import java.util.Hashtable;
import java.util.Vector;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.vamsas.client.ClientHandle;
import org.vamsas.client.Events;
import org.vamsas.client.IClient;
import org.vamsas.client.IClientDocument;
import org.vamsas.client.SessionHandle;
import org.vamsas.client.UserHandle;
+import org.vamsas.objects.core.VamsasDocument;
/**
* @author jimp
*/
public class SimpleClient implements IClient {
-
- UserHandle user = null;
-
- SessionHandle session = null;
-
- ClientHandle client = null;
+ private static Log log = LogFactory.getLog(SimpleClient.class);
+
+ protected UserHandle user = null;
+
+ protected SessionUrn session = null;
+ protected VamsasSession _session;
+ protected ClientHandle client = null;
+ protected EventGeneratorThread evgen = null;
+ IdFactory vorba = null;
+ private void makeVorbaIdFactory() {
+ if (vorba==null) {
+ vorba = new IdFactory(getSessionHandle(), client, user);
+ } else
+ log.debug("Vorba Id factory exists already.");
+ }
+
+ /**
+ * construct SimpleClient for user, client and VamsasSession directory
+ * use the SimpleClientFactory rather than this constructor directly.
+ * @param user
+ * @param client
+ * @param sess
+ */
+ protected SimpleClient(UserHandle user, ClientHandle client, VamsasSession sess) {
+ // TODO: validate user/client/session
+ _session = sess;
+ this.user = user;
+ this.client = client;
+ session = new SessionUrn(_session);
+ }
+ /**
+ * construct new session by importing objects from an existing vamsas document
+ * @param user
+ * @param client
+ * @param sess
+ * @param importingArchive
+ * @throws Exception IOExceptions for Session IO problems, and general Exception if importing document is invalid.
+ */
+ protected SimpleClient(UserHandle user, ClientHandle client, VamsasSession sess, File importingArchive) throws Exception {
+ this(user, client, sess);
+ makeVorbaIdFactory();
+ VamsasArchive sessdoc = _session.getVamsasDocument();
+ try {
+ VamsasArchiveReader odoc = new VamsasArchiveReader(importingArchive);
+ SimpleDocument sdoc = new SimpleDocument(vorba);
+ VamsasDocument doc = sdoc.getVamsasDocument(odoc);
+ sessdoc.putVamsasDocument(doc, vorba);
+ sessdoc.closeArchive();
+ } catch (Exception e) {
+ sessdoc.cancelArchive();
+ // write a dummy archive
+ throw new Exception("Failed to import data from "+importingArchive, e);
+ }
+ }
/*
* (non-Javadoc)
- * TODO: check that build substitution variables are correct
+ * TODO: LATER: check that build substitution variables are correct
* @see org.vamsas.client.IClient#getAbout()
*/
public String getAbout() {
return new String("VORBA SimpleClient version $version$ build $build$");
}
-
+
/*
* (non-Javadoc)
*
public String getSessionUrn() {
return session.getSessionUrn();
}
-
+
/*
* (non-Javadoc)
*
* @see org.vamsas.client.IClient#getSessionHandle()
*/
public SessionHandle getSessionHandle() {
- return session;
+ // TODO: eliminate SessionHandle ? need to refactor interfaces.
+ SessionHandle sh = new SessionHandle(session.getSessionUrn());
+ return sh;
}
-
+
/*
* (non-Javadoc)
*
public ClientHandle getClientHandle() {
return client;
}
-
+
/*
* (non-Javadoc)
*
public UserHandle getUserHandle() {
return user;
}
-
+
private Hashtable handlers = initHandlers();
-
+
private Vector listeners = new Vector();
-
+
/**
* make all the PropertyChangeSupport objects for the
* events described in org.vamsas.client.Event
}
return events;
}
-
+
/*
* (non-Javadoc)
*
if (handlers.containsKey(Events.DOCUMENT_UPDATE)) {
Object handler;
((PropertyChangeSupport) (handler = handlers.get(Events.DOCUMENT_UPDATE)))
- .addPropertyChangeListener(evt);
+ .addPropertyChangeListener(evt);
listeners.add(handler);
listeners.add((Object) evt);
}
}
-
+ boolean finalized=false;
/*
* (non-Javadoc)
*
// deregister listeners.
// mark this instance as finalized
}
-
+
/**
* extract data appropriate for client, session and user
* from vamsas document.
*
* @see org.vamsas.client.IClient#getClientDocument()
*/
- public IClientDocument getClientDocument() {
+ public IClientDocument getClientDocument() throws IOException {
+
+ makeVorbaIdFactory();
+ VamsasArchive va = _session.getVamsasDocument();
+ // TODO: reduce size of vorba ids generated from these parameters to IdFactory (mainly sessionHandle rationalization ?)
+ try {
+ va.getOriginalVamsasDocument(va, vorba);
+ // if session currently holds data - read it in.
+ }
+ catch (Exception e) {
+
+ }
+ //ClientDocument cdoc = new ClientDocument();
+
Object[] vdoc;// TODO: = getVamsasDocument(new Reader());
// ClientDocument cdoc = new ClientDocument(getApplicationData(),
- // ((VamsasDocument) vdoc[0]).getVAMSAS(), (Hashtable) vdoc[1], this);
+ // ((VamsasDocument) vdoc[0]).getVAMSAS(), (Hashtable) vdoc[1], this);
//
return null;
}
-
+
/*
* (non-Javadoc)
*
* @see org.vamsas.client.IClient#updateDocument(org.vamsas.client.IClientDocument)
*/
public void updateDocument(IClientDocument newdoc) {
- // TODO Auto-generated method stub
- //
+ if (!(newdoc instanceof ClientDocument)) {
+ throw new Error("Invalid IClientDocument instance for SimpleClient.");
+ }
+
+ // try to update the sessionFile
+
+ // write the appHandle to the lastupdate file.
+
}
-
+
/*
* (non-Javadoc)
*
* @see org.vamsas.client.IClient#storeDocument(java.io.File)
*/
public void storeDocument(File location) {
- // TODO Auto-generated method stub
+ // write storeDocument file to inform other clients that they should raise
+ Lock vamlock = evgen.want_to_store();
+ // Events.DOCUMENT_FINALIZEAPPDATA
+ try {
+ _session.writeVamsasDocument(location, vamlock);
+ } catch (Exception e) {
+ log.warn("Exception whilst trying to store document in "+location,e);
+ }
+ vamlock.release();
}
-
+
/*
* (non-Javadoc)
*
if (handlers.containsKey(EventChain)) {
Object handler;
((PropertyChangeSupport) (handler = handlers.get(EventChain)))
- .addPropertyChangeListener(evt);
+ .addPropertyChangeListener(evt);
listeners.add(handler);
listeners.add((Object) evt);
}
}
-
+
/* (non-Javadoc)
* @see org.vamsas.client.IClient#pollUpdate()
*/
public void pollUpdate() {
- // TODO wake up UpdateWatcher thread to check for updates.
- }
-
- public static void main(String[] args) {
+ if (evgen==null) {
+ log.warn("pollUpdate called on incomplete SimpleClient object.");
+ return;
+ }
+ //TODO ensure event generator robustly handles these interrupts.
+ log.debug("interrrupting event generator.");
+ evgen.interrupt();
+ log.debug("interrrupted event generator.");
}
/* (non-Javadoc)
* @see org.vamsas.client.IClient#joinSession()
*/
public void joinSession() throws Exception {
- // TODO Auto-generated method stub
+ // start the EventGenerator thread.
+ if (evgen==null) {
+ log.warn("joinSession called on incomplete SimpleClient object.");
+ return;
+ }
+ if (evgen.isAlive())
+ throw new Error("Join session called twice for the same SimpleClient (IClient instance).");
+ evgen.start();
+ if (evgen.isAlive())
+ log.debug("Started EventGenerator thread.");
+ else {
+ log.warn("Failed to start EventGenerator thread.");
+ throw new Exception("Failed to start event generator thread - client cannot be instantiated.");
+ }
}
+
+
+
+ /* (non-Javadoc)
+ * @see org.vamsas.client.IClient#importDocument(java.io.File)
+ */
+ public void importDocument(File location) {
+ // TODO LATER: implement SimpleClient.importDocument()
+ log.error("importDocument is not implemented for a SimpleClient Session.");
+ }
}
import java.io.File;
import java.io.IOException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.vamsas.client.ClientHandle;
import org.vamsas.client.IClient;
import org.vamsas.client.IClientFactory;
*
*/
public class SimpleClientFactory implements IClientFactory {
-
+ private static Log log = LogFactory.getLog(SimpleClientFactory.class);
+
File sessionArena;
/**
// create client instance
return null;
}
-
+ /**
+ * make a new vamsas session from the data in the archive vamsasdocument
+ * @param applicationHandle
+ * @param userId
+ * @param vamsasdocument
+ * @return
+ */
+ public IClient openSession(ClientHandle applicationHandle, UserHandle userId, ArchiveUrn vamsasdocument) throws IOException {
+ // verify applicationHandle and userId
+ // TODO: verify applicationHandle and userId
+ // verify vamsasdocument is a valid document.
+ File vamdoc = vamsasdocument.asFile();
+ log.debug("Starting new session with data from "+vamsasdocument.getSessionUrn()+"(File is : "+vamdoc+")");
+ VamsasArchiveReader archive = new VamsasArchiveReader(vamdoc);
+ // TODO: a real validity test. The below just checks it can be read.
+ if (!archive.isValid())
+ throw new IOException(vamsasdocument.getSessionUrn()+" is not a valid vamsasDocument archive.");
+ // create new session directory
+ if (sessionArena==null)
+ throw new Error("Improperly initialised SimpleClientFactory object - null sessionArena.");
+ File sessdir = File.createTempFile("sess", ".simpleclient", sessionArena);
+ if (!(sessdir.delete() && sessdir.mkdir()))
+ throw new IOException("Could not make session directory "+sessdir);
+ VamsasSession sess = new VamsasSession(sessdir);
+ // copy document into session directory
+ sess.setVamsasDocument(vamsasdocument.asFile());
+ // create client instance and return.
+ SimpleClient client = new SimpleClient(userId, applicationHandle, sess);
+
+ return client;
+ }
public static void main(String[] args) {
}
}
/**
* holds static vamsasDocument from XML routines and
* state objects for a particular unmarshalled Document instance.
+ * TODO Could be refactored ? only dependence is the IdFactory.getDummyFactory(String) method.
* @author jimp
- *
*/
public class SimpleDocument {
VorbaIdFactory vorba;
package org.vamsas.client.simpleclient;
+import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Writer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Appender;
import org.apache.log4j.Logger;
import org.apache.log4j.FileAppender;
+import org.vamsas.client.ClientHandle;
+import org.vamsas.client.UserHandle;
/**
+ * Holds the file handlers and watchers for a session.
*
+ * TODO: delete the stuff below - prolly irrelevant now
* Vamsas client is intialised with a path to create live session directories.
* This path may contain a vamsas.properties file
* that sets additional parameters (otherwise client
public class VamsasSession {
/**
- * Holds the file handlers and watchers for a session.
- */
- /**
- * TODO: make sessionDir signature to allow VamsasSession instances to recognise the form of the directory
- */
- /**
* indicator file for informing other processes that
* they should finalise their vamsas datasets for
- * storing in a vamsas archive.
+ * storing into a vamsas archive.
*/
- public static final String CLOSEANDSAVE_FILE="exiting";
+ public static final String CLOSEANDSAVE_FILE="stored.log";
/**
* log file location
*/
* @throws IOException
*/
private void initLog() throws IOException {
+ // TODO: LATER: make dedicated appender format for session log.
Appender app = slog.getAppender("SESSION_LOG");
slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
}
throw new IOException("Failed to make VamsasSession directory in "+sessionDir);
this.sessionDir = sessionDir;
createSessionFiles();
+ createDummyVamsasDocument();
initSessionObjects();
slog.debug("Session directory created.");
log.debug("Initialised VamsasSession in "+sessionDir);
throw new IOException("initSessionObjects called for initialised VamsasSession object.");
clist = new ClientsFile(new File(sessionDir,CLIENT_LIST));
vamArchive = new VamsasFile(new File(sessionDir,VAMSAS_OBJ));
+ initLog();
}
/**
* make a new watcher object for the clientFile
public FileWatcher getDocWatcher() {
return new FileWatcher(vamArchive.sessionFile);
}
-
+ FileWatcher store_doc_file=null;
/**
* make a new watcher object for the messages file
* @return new watcher instance
*/
public FileWatcher getStoreWatcher() {
- return new FileWatcher(new File(CLOSEANDSAVE_FILE));
+ return store_doc_file = new FileWatcher(new File(CLOSEANDSAVE_FILE));
+ }
+ /**
+ * write to the StoreWatcher file to indicate that a storeDocumentRequest has been made.
+ * The local client's storeWatcher FileWatcher object is updated so the initial change is not registered.
+ * @param client
+ * @param user
+ * @return
+ */
+ public void addStoreDocumentRequest(ClientHandle client, UserHandle user) throws IOException {
+ SessionFile sfw = new SessionFile(new File(sessionDir, CLOSEANDSAVE_FILE));
+ while (!sfw.lockFile())
+ log.debug("Trying to get lock for "+CLOSEANDSAVE_FILE);
+ sfw.fileLock.rafile.setLength(0); // wipe out any old info.
+ // TODO: rationalise what gets written to this file (ie do we want other clients to read the id of the requestor?)
+ sfw.fileLock.rafile.writeUTF(client.getClientUrn()+":"+user.getFullName()+"@"+user.getOrganization());
+ sfw.unlockFile();
+ if (store_doc_file!=null)
+ store_doc_file.setState();
+ slog.info("FinalizeAppData request from "+user.getFullName()+" using "+client.getClientUrn()+"");
+ }
+ /**
+ * create a new session with an existing vamsas Document - by copying it into the session.
+ * @param archive
+ */
+ protected void setVamsasDocument(File archive) throws IOException {
+ log.debug("Transferring vamsas data from "+archive+" to session:"+vamArchive.sessionFile);
+ SessionFile xtantdoc = new SessionFile(archive);
+ vamArchive.updateFrom(null, xtantdoc);
+ // TODO: LATER: decide if session archive provenance should be updated to reflect access.
+ // TODO: soon! do a proper import objects from external file
+ log.debug("Transfer complete.");
+ }
+ /**
+ * write session as a new vamsas Document (this will overwrite any existing file without warning)
+ * @param destarchive
+ */
+ protected void writeVamsasDocument(File destarchive, Lock extlock) throws IOException {
+ log.debug("Transferring vamsas data from "+vamArchive.sessionFile+" to session:"+destarchive);
+ SessionFile newdoc = new SessionFile(destarchive);
+ if (extlock==null && !vamArchive.lockFile())
+ while (!vamArchive.lockFile())
+ log.info("Trying to get lock for "+vamArchive.sessionFile);
+ // TODO: LATER: decide if session archive provenance should be written in vamsasDocument file for this export.
+ newdoc.updateFrom(extlock, vamArchive);
+ // TODO: SOON: fix use of updateFrom for file systems where locks cannot be made (because they don't have a lockManager, ie NFS/Unix, etc).
+ vamArchive.unLock();
+ newdoc.unlockFile();
+ log.debug("Transfer complete.");
+ }
+
+ /**
+ * Creates a VamsasArchive object for accessing and updating document
+ * Note: this will lock the Vamsas Document for exclusive access to the client.
+ * @return session vamsas document
+ * @throws IOException if locks fail or vamsas document read fails.
+ */
+ protected VamsasArchive getVamsasDocument() throws IOException {
+ // TODO: check we haven't already done this once
+ if (!vamArchive.lockFile())
+ throw new IOException("Failed to get lock for vamsas archive.");
+
+ VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true, vamArchive);
+
+ return va;
}
}