2 * Created on 15-Sep-2005
4 * TODO To change the template for this generated file go to
5 * Window - Preferences - Java - Code Style - Code Templates
7 package uk.ac.vamsas.client.simpleclient;
9 import java.beans.PropertyChangeListener;
10 import java.beans.PropertyChangeSupport;
12 import java.io.IOException;
13 import java.io.RandomAccessFile;
14 import java.nio.channels.FileChannel;
16 import java.nio.channels.OverlappingFileLockException;
17 import java.util.Hashtable;
18 import java.util.Vector;
20 import org.apache.commons.logging.Log;
21 import org.apache.commons.logging.LogFactory;
23 import uk.ac.vamsas.client.ClientHandle;
24 import uk.ac.vamsas.client.Events;
25 import uk.ac.vamsas.client.IClient;
26 import uk.ac.vamsas.client.IClientDocument;
27 import uk.ac.vamsas.client.IObjectUpdate;
28 import uk.ac.vamsas.client.InvalidSessionUrnException;
29 import uk.ac.vamsas.client.SessionHandle;
30 import uk.ac.vamsas.client.UserHandle;
31 import uk.ac.vamsas.client.picking.IPickManager;
32 import uk.ac.vamsas.objects.core.Entry;
33 import uk.ac.vamsas.objects.core.VamsasDocument;
34 import uk.ac.vamsas.objects.utils.ProvenanceStuff;
39 public class SimpleClient implements IClient {
41 private static Log log = LogFactory.getLog(SimpleClient.class);
43 protected UserHandle user = null;
45 protected SessionUrn session = null;
46 protected VamsasSession _session;
47 protected ClientHandle client = null;
48 protected EventGeneratorThread evgen = null;
49 protected ClientDocument cdocument = null;
54 private Lock activeClientFilelock = null;
55 private File clientlockFile = null;
58 * object hash table that persists in each client holding vorbaIds and hash values after a document write
60 protected Hashtable extantobjects=null;
62 * construct a transient IdFactory instance - this should last only as long as the
63 * SimpleClient object holds the lock on the vamsas document being created/manipulated.
66 private IdFactory makeVorbaIdFactory() {
67 if (extantobjects==null)
68 extantobjects=new Hashtable();
69 return new IdFactory(getSessionHandle(), client, user, extantobjects);
73 * construct SimpleClient for user, client and VamsasSession directory
74 * use the SimpleClientFactory rather than this constructor directly.
79 protected SimpleClient(UserHandle user, ClientHandle client, VamsasSession sess) throws InvalidSessionUrnException {
80 // TODO: validate user/client/session
85 log.debug("Creating new session for "+_session);
86 session = new SessionUrn(_session);
87 log.debug("Creating new Event Generator");
88 evgen = new EventGeneratorThread(_session, this, handlers);
89 /*} catch (MalformedURLException e) {
90 log.error("Couldn't form a valid SessionUrn object!",e);
91 throw new InvalidSessionUrnException(_session.toString());
93 log.debug("SimpleClient constructed for session "+session.getSessionUrn());
97 * construct new session by importing objects from an existing vamsas document
101 * @param importingArchive
102 * @throws Exception IOExceptions for Session IO problems, and general Exception if importing document is invalid.
104 protected SimpleClient(UserHandle user, ClientHandle client, VamsasSession sess, File importingArchive) throws Exception {
105 this(user, client, sess);
106 if (log.isDebugEnabled())
108 log.debug("Attempting to create a new session from file: "+importingArchive);
110 initSessionDocFromFile(importingArchive);
111 // TODO: write provenance entry for new session indicating the import.
114 private void initSessionDocFromFile(File importingArchive) throws Exception {
115 VamsasArchive sessdoc = _session.getVamsasDocument();
117 VamsasArchiveReader odoc = new VamsasArchiveReader(importingArchive);
118 SimpleDocument sdoc = new SimpleDocument(makeVorbaIdFactory());
119 VamsasDocument doc = sdoc.getVamsasDocument(odoc);
120 sessdoc.putVamsasDocument(doc, sdoc.vorba);
121 sessdoc.closeArchive();
122 log.debug("Imported new vamsas data from "+importingArchive);
124 } catch (Exception e) {
125 sessdoc.cancelArchive();
126 // TODO: check that new session is really writeable if the imported document was invalid ? or is removed
127 _session.slog.info("Exception when importing document data from "+importingArchive);
128 log.warn("While importing session data from existing archive in "+importingArchive, e);
129 throw new Exception("Failed to import data from "+importingArchive, e);
135 * LATER: check that build substitution variables are correct
136 * @see uk.ac.vamsas.client.IClient#getAbout()
138 public String getAbout() {
139 return new String("VORBA SimpleClient version $version$ build $build$");
145 * @see uk.ac.vamsas.client.IClient#getSessionUrn()
147 public String getSessionUrn() {
148 return session.getSessionUrn();
154 * @see uk.ac.vamsas.client.IClient#getSessionHandle()
156 public SessionHandle getSessionHandle() {
157 // TODO: eliminate SessionHandle ? need to refactor interfaces.
158 SessionHandle sh = new SessionHandle(session.getSessionUrn());
165 * @see uk.ac.vamsas.client.IClient#getClientHandle()
167 public ClientHandle getClientHandle() {
174 * @see uk.ac.vamsas.client.IClient#getUserHandle()
176 public UserHandle getUserHandle() {
181 * @return user field for a provenance entry
183 protected String getProvenanceUser() {
184 return new String(user.getFullName());
187 * construct a provenance entry for this client with the specified action string.
189 * @return properly completed provenance entry
191 protected Entry getProvenanceEntry(String action) {
192 Entry prov = ProvenanceStuff.newProvenanceEntry(client.getClientUrn(), getProvenanceUser(), action);
195 private Hashtable handlers = initHandlers();
197 private Vector listeners = new Vector();
200 * make all the PropertyChangeSupport objects for the
201 * events described in uk.ac.vamsas.client.Event
204 private static Hashtable initHandlers() {
205 Hashtable events = new Hashtable();
206 java.util.Iterator evt = Events.EventList.iterator();
207 while (evt.hasNext()) {
208 Object ths = evt.next();
209 events.put(ths, (Object) new PropertyChangeSupport(ths));
217 * @see uk.ac.vamsas.client.IClient#addDocumentUpdateHandler(java.util.EventListener)
219 public void addDocumentUpdateHandler(PropertyChangeListener evt) {
220 this.addVorbaEventHandler(Events.DOCUMENT_UPDATE, evt);
222 boolean finalized=false;
223 private void haltPickmanager() {
224 if (pickmanager!=null) {
225 final SimpleClient dying=this;
228 SimpleClient.log.debug("Stopping pickManager..");
229 dying.pickmanager.shutdown();
230 SimpleClient.log.debug("pickManager halted.");
238 * @see uk.ac.vamsas.client.IClient#finalizeClient()
240 public void finalizeClient() {
242 throw new Error("VAMSAS Client Implementation Error: Finalized called twice for same client instance.");
244 // mark this instance as finalized
247 // TODO: determine if this is last client in session
249 // TODO: raise events like : ((lst_client && document.request.to.close), (client_finalization), (
250 evgen._raise(Events.CLIENT_FINALIZATION, null, this,null);
251 // if (handlers.containsKey(Events.))
252 // if (handlers.containsKey(Events.CLIENT_FINALIZATION))
253 // deregister listeners.
254 log.debug("Stopping pickManager");
257 log.debug("Deregistering Client");
258 _session.removeClient(this);
259 //log.debug("Stopping EventGenerator..");
260 //evgen.stopWatching();
261 this.cdocument = null;
262 SimpleClient.log.debug("EventGenerator halted.");
263 log.debug("finalization Complete.");
269 * @see uk.ac.vamsas.client.IClient#getClientDocument()
271 public IClientDocument getClientDocument() throws IOException {
272 log.debug("getClientDocument");
273 if (cdocument!=null) {
274 // cdocument is non-nill if the ClientDocument.finalise() method hasn't been called.
277 evgen.disableDocumentWatch();
278 VamsasArchive va = null;
280 // LATER: bail out if it takes too long to get the lock ?
281 va = _session.getVamsasDocument();
283 catch (IOException e) {
284 throw new IOException("Failed to get lock on session document");
286 VamsasDocument doc=null;
287 IdFactory vorba = null;
288 // TODO: LATER: reduce size of vorba ids generated from these parameters to IdFactory (mainly sessionHandle rationalization ?)
290 va.setVorba(vorba=makeVorbaIdFactory());
291 // if session currently holds data - read it in - or get a dummy
292 log.debug("Accessing document");
294 va.getVamsasDocument(getProvenanceUser(),
295 "created new session document.", null);
297 log.debug("Successfully retrieved document.");
299 log.error("Unexpectedly retrieved null document!");
301 catch (Exception e) {
302 log.error("Failed to get session document for session directory '"+_session.sessionDir+"'", e);
303 throw new IOException("Failed to get session document for session directory '"+_session.sessionDir+"'");
305 // Construct the IClientDocument instance
307 cdocument = new ClientDocument(doc, va, vorba, this);
313 * @throws Errors for invalid newdoc parameter
314 * @see uk.ac.vamsas.client.IClient#updateDocument(uk.ac.vamsas.client.IClientDocument)
316 public void updateDocument(IClientDocument newdoc) {
317 log.debug("updateDocument:");
318 // Check validity of simpleclient instance and that it holds a lock on the session's document
319 if (!(newdoc instanceof ClientDocument)) {
320 throw new Error("Invalid IClientDocument passsed to SimpleClient.");
323 throw new Error("Client Error - updateDocument() called before getClientDocument() on this SimpleClient instance.");
324 if (newdoc!=cdocument)
325 throw new Error("Client Error - SimpleClient.updateDocument() can only take the IClientDocument instance returned from SimpleClient.getClientDocument()");
327 if (evgen.isDocumentWatchEnabled())
328 throw new Error("Probable Client Error (did you remember to call SimpleClient.updateDocument(clientdoc) at the end of the document update handler?) - or Library Bug : Document watcher still enabled whilst ClientDocument instance exists.");
330 if (!cdocument.isModified()) {
331 // client document is silently got rid of, with no session update events.
332 if (log.isDebugEnabled())
333 log.debug("updateDocument for "+session.getSessionUrn()+" with unmodified IClientDocument (skipping the write)");
335 writeSessionDocument();
337 tidyAwaySessionDocumentState();
340 * garbage collect the ClientDocument instance and re-enable watchers.
342 protected void tidyAwaySessionDocumentState() {
344 log.debug("Finalizing ClientDocument instance.");
345 cdocument.finalize();
346 } catch (Throwable e) {
347 log.error("Exception when trying to garbage collect ClientDocument for "+session.getSessionUrn(), e);
349 cdocument = null; // this is probably done by finalize
352 _session.unlockVamsasDocument();
353 evgen.enableDocumentWatch();
354 } catch (IOException e) {
355 log.warn("IO Problems when releasing lock on session document!",e);
356 _session.slog.error("IO problems when attempting to release lock on session document.");
361 * write the cdocument instance to the session for real.
363 private void writeSessionDocument() {
365 boolean updated=cdocument.updateSessionDocument();
367 log.warn("Session document did not update properly for session directory "+_session.sessionDir);
368 // cdocument.archive.cancelArchive(); // LATER: could be done - would need to prevent updateSessionDocument closing the iohandler.
369 _session.slog.warn("Session Document updated but may not be valid (false return from org.vamsas.simpleclient.ClientDocument.updateSessionDocument()");
371 log.debug("Document update successful.");
373 _session.setUnsavedFlag();
375 catch (IOException e) {
376 log.warn("IO Problems when updating document!",e);
377 _session.slog.error("IO problems when attempting to update document.");
384 * @see uk.ac.vamsas.client.IClient#storeDocument(java.io.File)
386 public void storeDocument(File location) {
388 throw new Error("Vamsas Client API Usage Error: storeDocument called with null location.");
389 log.debug("StoreDocument to "+location);
390 // write storeDocument file to inform other clients that they should raise
391 Lock vamlock = evgen.want_to_store();
392 // Events.DOCUMENT_FINALIZEAPPDATA
394 _session.writeVamsasDocument(location, vamlock);
395 _session.clearUnsavedFlag();
396 } catch (Exception e) {
397 log.warn("Exception whilst trying to store document in "+location,e);
405 * @see uk.ac.vamsas.client.IClient#addVorbaEventHandler(java.lang.String,
406 * java.beans.PropertyChangeListener)
408 public void addVorbaEventHandler(String EventChain, PropertyChangeListener evt) {
409 if (handlers.containsKey(EventChain)) {
410 log.debug("Adding new handler for "+EventChain);
412 ((PropertyChangeSupport) (handler = handlers.get(EventChain)))
413 .addPropertyChangeListener(evt);
414 listeners.add(handler);
415 listeners.add((Object) evt);
420 * @see uk.ac.vamsas.client.IClient#pollUpdate()
422 public void pollUpdate() {
423 log.debug("pollUpdate");
425 log.warn("pollUpdate called on incomplete SimpleClient object.");
429 if (!evgen.isWatcherAlive()) {
430 log.warn("pollUpdate called before joinSession() - trying to do this.");
433 } catch (Exception e) {
434 log.error("Unexpected exception on default call to joinSession",e);
438 //TODO ensure event generator robustly handles these interrupts.
439 log.debug("interrrupting event generator.");
440 evgen.interruptWatching();
441 log.debug("interrrupted event generator.");
445 * @see uk.ac.vamsas.client.IClient#joinSession()
447 public void joinSession() throws Exception {
448 log.debug("Joining Session.");
449 // start the EventGenerator thread.
451 log.warn("joinSession called on incomplete SimpleClient object.");
454 if (evgen.isWatcherAlive())
455 throw new Error("Join session called twice for the same SimpleClient (IClient instance).");
456 evgen.startWatching();
457 if (evgen.isWatcherAlive())
458 log.debug("Started EventGenerator thread.");
460 log.warn("Failed to start EventGenerator thread.");
461 throw new Exception("Failed to start event generator thread - client cannot be instantiated.");
463 if (evgen.countHandlersFor(Events.DOCUMENT_CREATE)>0) {
464 //TODO: LATER: is this application connecting to a newly created session document ?
465 //evgen.raise(Events.DOCUMENT_CREATE);
473 * @see uk.ac.vamsas.client.IClient#importDocument(java.io.File)
475 public void importDocument(File location) {
476 // TODO LATER: implement SimpleClient.importDocument()
477 // if (numberOfClients<1 or document file is empty/new, then can import directly.)
478 // more complex if data is already present in document. Could have a 'clearSession' method, too, or dump and overwrite instead.
479 log.error("importDocument is not yet implemented for a SimpleClient Session.");
482 this._session.setVamsasDocument(location);
483 } catch (IOException e) {
484 log.error("importDocument failed.");
488 public IObjectUpdate getUpdateHandler(Class rootObject) {
489 // TODO Auto-generated method stub
493 public IObjectUpdate[] getUpdateHandlers() {
494 // TODO Auto-generated method stub
498 public void removeUpdateHandler(Class rootObject) {
499 // TODO Auto-generated method stub
503 public void setUpdateHandler(IObjectUpdate handler) {
504 // TODO Auto-generated method stub
509 * retrieves the current VamsasSession to which belong the client
510 * @return the _session
512 protected VamsasSession get_session() {
513 return this._session;
515 SimplePickManager pickmanager=null;
517 * @see uk.ac.vamsas.client.IClient#getPickManager()
519 public IPickManager getPickManager() {
524 private void createPickManager() {
525 if (pickmanager==null){
526 // TODO: Construct PickManager for session using details from sessionURN!
527 log.debug("Creating PickManager (not from sessionURN yet)");
528 pickmanager = new SimplePickManager(new uk.ac.vamsas.client.picking.SocketManager());
533 protected void releaseActiveClientFile() throws IOException
536 log.debug("Releasing active client locks");
537 if( activeClientFilelock != null)
539 log.debug("Releasing lock on active client lock file");
540 activeClientFilelock.release();
541 log.debug("ReleaseActiveClientFile called when client has no lock on its clientLockFile");
542 activeClientFilelock = null;
544 log.debug("ReleaseActiveClientFile called when client has no lock on its clientLockFile");
546 if (this.clientlockFile != null)
548 log.debug("trying to delete active client lock file");
549 if (this.clientlockFile.exists())
551 this.clientlockFile.delete();
552 log.debug("deleted active client lock file");
555 log.debug("ReleaseActiveClientFile called when client has no clientLockFile");
559 protected void createActiveClientFile() throws IOException
561 if(this.clientlockFile != null )return;
562 log.debug("createActiveClientFile");
563 //create, if need, subdirectory to contain client files
564 File clientlockFileDir = new File ( this.get_session().sessionDir, this.get_session().clientFileDirectory);
565 if( !clientlockFileDir.exists())
566 {//the directory does not exist, create it
567 if (! clientlockFileDir.mkdirs())
569 throw new IOException("Failed to create sub directory to session directory for client lock files'"+clientlockFileDir.getAbsolutePath()+"'");
574 if (!(clientlockFileDir.isDirectory() && clientlockFileDir.canWrite()))
576 throw new IOException("Directory for client lock files is not a directory or is not accessibl: '"+clientlockFileDir.getAbsolutePath()+"'");
579 this.clientlockFile = new File (clientlockFileDir, this.getClientHandle().getClientUrn().replaceAll("[:;/\\\\]+",""));
581 log.debug("Creating active client lock file "+ this.clientlockFile.getAbsolutePath());
582 Lock clientLock = uk.ac.vamsas.client.simpleclient.LockFactory.getLock(clientlockFile, false);
583 if (clientLock==null || !clientLock.isLocked())
585 log.fatal("IMPLEMENTATION ERROR: Couldn't get a lock for the client lock file "+clientlockFile);
587 activeClientFilelock = clientLock;
590 * @return the clientlockFile
592 protected File getClientlockFile() {
593 return clientlockFile;
597 * @return the lock for the client in the session
599 protected Lock getClientLock() {
600 return activeClientFilelock;