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 VamsasArchive sessdoc = _session.getVamsasDocument();
108 VamsasArchiveReader odoc = new VamsasArchiveReader(importingArchive);
109 SimpleDocument sdoc = new SimpleDocument(makeVorbaIdFactory());
110 VamsasDocument doc = sdoc.getVamsasDocument(odoc);
111 sessdoc.putVamsasDocument(doc, sdoc.vorba);
112 sessdoc.closeArchive();
113 log.debug("Imported new vamsas data from "+importingArchive);
114 } catch (Exception e) {
115 sessdoc.cancelArchive();
116 // write a dummy iohandler
117 _session.slog.info("Exception when importing document data from "+importingArchive);
118 log.warn("While importing session data from existing archive in "+importingArchive, e);
119 throw new Exception("Failed to import data from "+importingArchive, e);
125 * LATER: check that build substitution variables are correct
126 * @see uk.ac.vamsas.client.IClient#getAbout()
128 public String getAbout() {
129 return new String("VORBA SimpleClient version $version$ build $build$");
135 * @see uk.ac.vamsas.client.IClient#getSessionUrn()
137 public String getSessionUrn() {
138 return session.getSessionUrn();
144 * @see uk.ac.vamsas.client.IClient#getSessionHandle()
146 public SessionHandle getSessionHandle() {
147 // TODO: eliminate SessionHandle ? need to refactor interfaces.
148 SessionHandle sh = new SessionHandle(session.getSessionUrn());
155 * @see uk.ac.vamsas.client.IClient#getClientHandle()
157 public ClientHandle getClientHandle() {
164 * @see uk.ac.vamsas.client.IClient#getUserHandle()
166 public UserHandle getUserHandle() {
171 * @return user field for a provenance entry
173 protected String getProvenanceUser() {
174 return new String(user.getFullName());
177 * construct a provenance entry for this client with the specified action string.
179 * @return properly completed provenance entry
181 protected Entry getProvenanceEntry(String action) {
182 Entry prov = ProvenanceStuff.newProvenanceEntry(client.getClientUrn(), getProvenanceUser(), action);
185 private Hashtable handlers = initHandlers();
187 private Vector listeners = new Vector();
190 * make all the PropertyChangeSupport objects for the
191 * events described in uk.ac.vamsas.client.Event
194 private static Hashtable initHandlers() {
195 Hashtable events = new Hashtable();
196 java.util.Iterator evt = Events.EventList.iterator();
197 while (evt.hasNext()) {
198 Object ths = evt.next();
199 events.put(ths, (Object) new PropertyChangeSupport(ths));
207 * @see uk.ac.vamsas.client.IClient#addDocumentUpdateHandler(java.util.EventListener)
209 public void addDocumentUpdateHandler(PropertyChangeListener evt) {
210 this.addVorbaEventHandler(Events.DOCUMENT_UPDATE, evt);
212 boolean finalized=false;
213 private void haltPickmanager() {
214 if (pickmanager!=null) {
215 final SimpleClient dying=this;
218 SimpleClient.log.debug("Stopping pickManager..");
219 dying.pickmanager.shutdown();
220 SimpleClient.log.debug("pickManager halted.");
228 * @see uk.ac.vamsas.client.IClient#finalizeClient()
230 public void finalizeClient() {
232 throw new Error("VAMSAS Client Implementation Error: Finalized called twice for same client instance.");
234 // mark this instance as finalized
237 // TODO: determine if this is last client in session
239 // TODO: raise events like : ((lst_client && document.request.to.close), (client_finalization), (
240 evgen._raise(Events.CLIENT_FINALIZATION, null, this,null);
241 // if (handlers.containsKey(Events.))
242 // if (handlers.containsKey(Events.CLIENT_FINALIZATION))
243 // deregister listeners.
244 log.debug("Stopping pickManager");
247 log.debug("Deregistering Client");
248 _session.removeClient(this);
249 //log.debug("Stopping EventGenerator..");
250 //evgen.stopWatching();
251 SimpleClient.log.debug("EventGenerator halted.");
252 this.cdocument = null;
253 log.debug("finalization Complete.");
259 * @see uk.ac.vamsas.client.IClient#getClientDocument()
261 public IClientDocument getClientDocument() throws IOException {
262 log.debug("getClientDocument");
263 if (cdocument!=null) {
264 // cdocument is non-nill if the ClientDocument.finalise() method hasn't been called.
267 evgen.disableDocumentWatch();
268 VamsasArchive va = null;
270 // LATER: bail out if it takes too long to get the lock ?
271 va = _session.getVamsasDocument();
273 catch (IOException e) {
274 throw new IOException("Failed to get lock on session document");
276 VamsasDocument doc=null;
277 IdFactory vorba = null;
278 // TODO: LATER: reduce size of vorba ids generated from these parameters to IdFactory (mainly sessionHandle rationalization ?)
280 va.setVorba(vorba=makeVorbaIdFactory());
281 // if session currently holds data - read it in - or get a dummy
282 log.debug("Accessing document");
284 va.getVamsasDocument(getProvenanceUser(),
285 "created new session document.", null);
287 log.debug("Successfully retrieved document.");
289 log.error("Unexpectedly retrieved null document!");
291 catch (Exception e) {
292 log.error("Failed to get session document for session directory '"+_session.sessionDir+"'", e);
293 throw new IOException("Failed to get session document for session directory '"+_session.sessionDir+"'");
295 // Construct the IClientDocument instance
297 cdocument = new ClientDocument(doc, va, vorba, this);
303 * @throws Errors for invalid newdoc parameter
304 * @see uk.ac.vamsas.client.IClient#updateDocument(uk.ac.vamsas.client.IClientDocument)
306 public void updateDocument(IClientDocument newdoc) {
307 log.debug("updateDocument:");
308 // Check validity of simpleclient instance and that it holds a lock on the session's document
309 if (!(newdoc instanceof ClientDocument)) {
310 throw new Error("Invalid IClientDocument passsed to SimpleClient.");
313 throw new Error("Client Error - updateDocument() called before getClientDocument() on this SimpleClient instance.");
314 if (newdoc!=cdocument)
315 throw new Error("Client Error - SimpleClient.updateDocument() can only take the IClientDocument instance returned from SimpleClient.getClientDocument()");
317 if (evgen.isDocumentWatchEnabled())
318 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.");
320 if (!cdocument.isModified()) {
321 // client document is silently got rid of, with no session update events.
322 if (log.isDebugEnabled())
323 log.debug("updateDocument for "+session.getSessionUrn()+" with unmodified IClientDocument (skipping the write)");
325 writeSessionDocument();
327 tidyAwaySessionDocumentState();
330 * garbage collect the ClientDocument instance and re-enable watchers.
332 protected void tidyAwaySessionDocumentState() {
334 log.debug("Finalizing ClientDocument instance.");
335 cdocument.finalize();
336 } catch (Throwable e) {
337 log.error("Exception when trying to garbage collect ClientDocument for "+session.getSessionUrn(), e);
339 cdocument = null; // this is probably done by finalize
342 _session.unlockVamsasDocument();
343 evgen.enableDocumentWatch();
344 } catch (IOException e) {
345 log.warn("IO Problems when releasing lock on session document!",e);
346 _session.slog.error("IO problems when attempting to release lock on session document.");
351 * write the cdocument instance to the session for real.
353 private void writeSessionDocument() {
355 boolean updated=cdocument.updateSessionDocument();
357 log.warn("Session document did not update properly for session directory "+_session.sessionDir);
358 // cdocument.archive.cancelArchive(); // LATER: could be done - would need to prevent updateSessionDocument closing the iohandler.
359 _session.slog.warn("Session Document updated but may not be valid (false return from org.vamsas.simpleclient.ClientDocument.updateSessionDocument()");
361 log.debug("Document update successful.");
363 _session.setUnsavedFlag();
365 catch (IOException e) {
366 log.warn("IO Problems when updating document!",e);
367 _session.slog.error("IO problems when attempting to update document.");
374 * @see uk.ac.vamsas.client.IClient#storeDocument(java.io.File)
376 public void storeDocument(File location) {
378 throw new Error("Vamsas Client API Usage Error: storeDocument called with null location.");
379 log.debug("StoreDocument to "+location);
380 // write storeDocument file to inform other clients that they should raise
381 Lock vamlock = evgen.want_to_store();
382 // Events.DOCUMENT_FINALIZEAPPDATA
384 _session.writeVamsasDocument(location, vamlock);
385 _session.clearUnsavedFlag();
386 } catch (Exception e) {
387 log.warn("Exception whilst trying to store document in "+location,e);
395 * @see uk.ac.vamsas.client.IClient#addVorbaEventHandler(java.lang.String,
396 * java.beans.PropertyChangeListener)
398 public void addVorbaEventHandler(String EventChain, PropertyChangeListener evt) {
399 if (handlers.containsKey(EventChain)) {
400 log.debug("Adding new handler for "+EventChain);
402 ((PropertyChangeSupport) (handler = handlers.get(EventChain)))
403 .addPropertyChangeListener(evt);
404 listeners.add(handler);
405 listeners.add((Object) evt);
410 * @see uk.ac.vamsas.client.IClient#pollUpdate()
412 public void pollUpdate() {
413 log.debug("pollUpdate");
415 log.warn("pollUpdate called on incomplete SimpleClient object.");
419 if (!evgen.isWatcherAlive()) {
420 log.warn("pollUpdate called before joinSession() - trying to do this.");
423 } catch (Exception e) {
424 log.error("Unexpected exception on default call to joinSession",e);
428 //TODO ensure event generator robustly handles these interrupts.
429 log.debug("interrrupting event generator.");
430 evgen.interruptWatching();
431 log.debug("interrrupted event generator.");
435 * @see uk.ac.vamsas.client.IClient#joinSession()
437 public void joinSession() throws Exception {
438 log.debug("Joining Session.");
439 // start the EventGenerator thread.
441 log.warn("joinSession called on incomplete SimpleClient object.");
444 if (evgen.isWatcherAlive())
445 throw new Error("Join session called twice for the same SimpleClient (IClient instance).");
446 evgen.startWatching();
447 if (evgen.isWatcherAlive())
448 log.debug("Started EventGenerator thread.");
450 log.warn("Failed to start EventGenerator thread.");
451 throw new Exception("Failed to start event generator thread - client cannot be instantiated.");
453 if (evgen.countHandlersFor(Events.DOCUMENT_CREATE)>0) {
454 //TODO: LATER: is this application connecting to a newly created session document ?
455 //evgen.raise(Events.DOCUMENT_CREATE);
463 * @see uk.ac.vamsas.client.IClient#importDocument(java.io.File)
465 public void importDocument(File location) {
466 // TODO LATER: implement SimpleClient.importDocument()
467 // if (numberOfClients<1 or document file is empty/new, then can import directly.)
468 // more complex if data is already present in document. Could have a 'clearSession' method, too, or dump and overwrite instead.
469 log.error("importDocument is not yet implemented for a SimpleClient Session.");
472 this._session.setVamsasDocument(location);
473 } catch (IOException e) {
474 log.error("importDocument failed.");
478 public IObjectUpdate getUpdateHandler(Class rootObject) {
479 // TODO Auto-generated method stub
483 public IObjectUpdate[] getUpdateHandlers() {
484 // TODO Auto-generated method stub
488 public void removeUpdateHandler(Class rootObject) {
489 // TODO Auto-generated method stub
493 public void setUpdateHandler(IObjectUpdate handler) {
494 // TODO Auto-generated method stub
499 * retrieves the current VamsasSession to which belong the client
500 * @return the _session
502 protected VamsasSession get_session() {
503 return this._session;
505 SimplePickManager pickmanager=null;
507 * @see uk.ac.vamsas.client.IClient#getPickManager()
509 public IPickManager getPickManager() {
514 private void createPickManager() {
515 if (pickmanager==null){
516 // TODO: Construct PickManager for session using details from sessionURN!
517 log.debug("Creating PickManager (not from sessionURN yet)");
518 pickmanager = new SimplePickManager(new uk.ac.vamsas.client.picking.SocketManager());
523 protected void releaseActiveClientFile() throws IOException
526 log.debug("Releasing active client locks");
527 if( activeClientFilelock != null)
529 log.debug("Releasing lock on active client lock file");
530 activeClientFilelock.release();
531 log.debug("ReleaseActiveClientFile called when client has no lock on its clientLockFile");
532 activeClientFilelock = null;
534 log.debug("ReleaseActiveClientFile called when client has no lock on its clientLockFile");
536 if (this.clientlockFile != null)
538 log.debug("trying to delete active client lock file");
539 if (this.clientlockFile.exists())
541 this.clientlockFile.delete();
542 log.debug("deleted active client lock file");
545 log.debug("ReleaseActiveClientFile called when client has no clientLockFile");
549 protected void createActiveClientFile() throws IOException
551 if(this.clientlockFile != null )return;
552 log.debug("createActiveClientFile");
553 //create, if need, subdirectory to contain client files
554 File clientlockFileDir = new File ( this.get_session().sessionDir, this.get_session().clientFileDirectory);
555 if( !clientlockFileDir.exists())
556 {//the directory does not exist, create it
557 if (! clientlockFileDir.mkdirs())
559 throw new IOException("Failed to create sub directory to session directory for client lock files'"+clientlockFileDir.getAbsolutePath()+"'");
564 if (!(clientlockFileDir.isDirectory() && clientlockFileDir.canWrite()))
566 throw new IOException("Directory for client lock files is not a directory or is not accessibl: '"+clientlockFileDir.getAbsolutePath()+"'");
569 this.clientlockFile = new File (clientlockFileDir, this.getClientHandle().getClientUrn().replaceAll("[:;/\\\\]+",""));
571 log.debug("Creating active client lock file "+ this.clientlockFile.getAbsolutePath());
572 Lock clientLock = uk.ac.vamsas.client.simpleclient.LockFactory.getLock(clientlockFile, false);
573 if (clientLock==null || !clientLock.isLocked())
575 log.fatal("IMPLEMENTATION ERROR: Couldn't get a lock for the client lock file "+clientlockFile);
577 activeClientFilelock = clientLock;
580 * @return the clientlockFile
582 protected File getClientlockFile() {
583 return clientlockFile;
587 * @return the lock for the client in the session
589 protected Lock getClientLock() {
590 return activeClientFilelock;