2 * This file is part of the Vamsas Client version 0.2.
3 * Copyright 2010 by Jim Procter, Iain Milne, Pierre Marguerite,
4 * Andrew Waterhouse and Dominik Lindner.
6 * Earlier versions have also been incorporated into Jalview version 2.4
7 * since 2008, and TOPALi version 2 since 2007.
9 * The Vamsas Client is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
14 * The Vamsas Client is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with the Vamsas Client. If not, see <http://www.gnu.org/licenses/>.
22 package uk.ac.vamsas.client.simpleclient;
24 import java.beans.PropertyChangeListener;
25 import java.beans.PropertyChangeSupport;
27 import java.io.IOException;
28 import java.io.RandomAccessFile;
29 import java.nio.channels.FileChannel;
31 import java.nio.channels.OverlappingFileLockException;
32 import java.util.Hashtable;
33 import java.util.Vector;
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
38 import uk.ac.vamsas.client.ClientHandle;
39 import uk.ac.vamsas.client.Events;
40 import uk.ac.vamsas.client.IClient;
41 import uk.ac.vamsas.client.IClientDocument;
42 import uk.ac.vamsas.client.IObjectUpdate;
43 import uk.ac.vamsas.client.InvalidSessionDocumentException;
44 import uk.ac.vamsas.client.InvalidSessionUrnException;
45 import uk.ac.vamsas.client.SessionHandle;
46 import uk.ac.vamsas.client.UserHandle;
47 import uk.ac.vamsas.client.picking.IPickManager;
48 import uk.ac.vamsas.objects.core.Entry;
49 import uk.ac.vamsas.objects.core.VamsasDocument;
50 import uk.ac.vamsas.objects.utils.ProvenanceStuff;
55 public class SimpleClient implements IClient {
57 private static Log log = LogFactory.getLog(SimpleClient.class);
59 protected UserHandle user = null;
61 protected SessionUrn session = null;
63 protected VamsasSession _session;
65 protected ClientHandle client = null;
67 protected EventGeneratorThread evgen = null;
69 protected ClientDocument cdocument = null;
71 private Lock activeClientFilelock = null;
73 private File clientlockFile = null;
76 * object hash table that persists in each client holding vorbaIds and hash
77 * values after a document write
79 protected Hashtable extantobjects = null;
82 * construct a transient IdFactory instance - this should last only as long as
83 * the SimpleClient object holds the lock on the vamsas document being
84 * created/manipulated.
88 private IdFactory makeVorbaIdFactory() {
89 if (extantobjects == null)
90 extantobjects = new Hashtable();
91 return new IdFactory(getSessionHandle(), client, user, extantobjects);
95 * construct SimpleClient for user, client and VamsasSession directory use the
96 * SimpleClientFactory rather than this constructor directly.
102 protected SimpleClient(UserHandle user, ClientHandle client,
103 VamsasSession sess) throws InvalidSessionUrnException {
104 // TODO: validate user/client/session
107 this.client = client;
109 log.debug("Creating new session for " + _session);
110 session = new SessionUrn(_session);
111 log.debug("Creating new Event Generator");
112 evgen = new EventGeneratorThread(_session, this, handlers);
114 * } catch (MalformedURLException e) {
115 * log.error("Couldn't form a valid SessionUrn object!",e); throw new
116 * InvalidSessionUrnException(_session.toString()); }
119 .debug("SimpleClient constructed for session "
120 + session.getSessionUrn());
125 * construct new SimpleClientsession by importing objects from an existing
131 * @param importingArchive
133 * IOExceptions for Session IO problems, and general Exception if
134 * importing document is invalid. protected SimpleClient(UserHandle
135 * user, ClientHandle client, VamsasSession sess, File
136 * importingArchive) throws Exception { this(user, client, sess); if
137 * (log.isDebugEnabled()) {
138 * log.debug("Attempting to overwrite session document with file: "
139 * +importingArchive); } // TODO: write provenance entry for new
140 * session indicating the import.
146 * (non-Javadoc) LATER: check that build substitution variables are correct
148 * @see uk.ac.vamsas.client.IClient#getAbout()
150 public String getAbout() {
151 return new String("VORBA SimpleClient version $version$ build $build$");
157 * @see uk.ac.vamsas.client.IClient#getSessionUrn()
159 public String getSessionUrn() {
160 return session.getSessionUrn();
166 * @see uk.ac.vamsas.client.IClient#getSessionHandle()
168 public SessionHandle getSessionHandle() {
169 // TODO: eliminate SessionHandle ? need to refactor interfaces.
170 SessionHandle sh = new SessionHandle(session.getSessionUrn());
177 * @see uk.ac.vamsas.client.IClient#getClientHandle()
179 public ClientHandle getClientHandle() {
186 * @see uk.ac.vamsas.client.IClient#getUserHandle()
188 public UserHandle getUserHandle() {
194 * @return user field for a provenance entry
196 protected String getProvenanceUser() {
197 return new String(user.getFullName());
201 * construct a provenance entry for this client with the specified action
205 * @return properly completed provenance entry
207 protected Entry getProvenanceEntry(String action) {
208 Entry prov = ProvenanceStuff.newProvenanceEntry(client.getClientUrn(),
209 getProvenanceUser(), action);
213 private Hashtable handlers = initHandlers();
215 private Vector listeners = new Vector();
218 * make all the PropertyChangeSupport objects for the events described in
219 * uk.ac.vamsas.client.Event
223 private static Hashtable initHandlers() {
224 Hashtable events = new Hashtable();
225 java.util.Iterator evt = Events.EventList.iterator();
226 while (evt.hasNext()) {
227 Object ths = evt.next();
228 events.put(ths, (Object) new PropertyChangeSupport(ths));
237 * uk.ac.vamsas.client.IClient#addDocumentUpdateHandler(java.util.EventListener
240 public void addDocumentUpdateHandler(PropertyChangeListener evt) {
241 this.addVorbaEventHandler(Events.DOCUMENT_UPDATE, evt);
244 boolean finalized = false;
246 private void haltPickmanager() {
247 if (pickmanager != null) {
248 final SimpleClient dying = this;
251 SimpleClient.log.debug("Stopping pickManager..");
252 dying.pickmanager.shutdown();
253 SimpleClient.log.debug("pickManager halted.");
262 * @see uk.ac.vamsas.client.IClient#finalizeClient()
264 public void finalizeClient() {
267 "VAMSAS Client Implementation Error: Finalized called twice for same client instance.");
269 // mark this instance as finalized
272 // TODO: determine if this is last client in session
274 // TODO: raise events like : ((lst_client && document.request.to.close),
275 // (client_finalization), (
276 evgen._raise(Events.CLIENT_FINALIZATION, null, this, null);
277 // if (handlers.containsKey(Events.))
278 // if (handlers.containsKey(Events.CLIENT_FINALIZATION))
279 // deregister listeners.
280 log.debug("Stopping pickManager");
283 log.debug("Deregistering Client");
284 _session.removeClient(this);
285 // log.debug("Stopping EventGenerator..");
286 // evgen.stopWatching();
287 this.cdocument = null;
288 SimpleClient.log.debug("EventGenerator halted.");
289 log.debug("finalization Complete.");
295 * @see uk.ac.vamsas.client.IClient#getClientDocument()
297 public IClientDocument getClientDocument() throws IOException {
298 log.debug("getClientDocument");
299 if (cdocument != null) {
300 // cdocument is non-nill if the ClientDocument.finalise() method hasn't
304 evgen.disableDocumentWatch();
305 VamsasArchive va = null;
307 // LATER: bail out if it takes too long to get the lock ?
308 va = _session.getVamsasDocument();
309 } catch (IOException e) {
310 throw new IOException("Failed to get lock on session document");
312 VamsasDocument doc = null;
313 IdFactory vorba = null;
314 // TODO: LATER: reduce size of vorba ids generated from these parameters to
315 // IdFactory (mainly sessionHandle rationalization ?)
317 va.setVorba(vorba = makeVorbaIdFactory());
318 // if session currently holds data - read it in - or get a dummy
319 log.debug("Accessing document");
320 doc = va.getVamsasDocument(getProvenanceUser(),
321 "created new session document.", null);
323 log.debug("Successfully retrieved document.");
325 log.error("Unexpectedly retrieved null document!");
326 } catch (Exception e) {
327 log.error("Failed to get session document for session directory '"
328 + _session.sessionDir + "'", e);
329 throw new IOException(
330 "Failed to get session document for session directory '"
331 + _session.sessionDir + "'");
333 // Construct the IClientDocument instance
335 cdocument = new ClientDocument(doc, va, vorba, this);
342 * @throws Errors for invalid newdoc parameter
345 * uk.ac.vamsas.client.IClient#updateDocument(uk.ac.vamsas.client.IClientDocument
348 public void updateDocument(IClientDocument newdoc) {
349 log.debug("updateDocument:");
350 // Check validity of simpleclient instance and that it holds a lock on the
351 // session's document
352 if (!(newdoc instanceof ClientDocument)) {
353 throw new Error("Invalid IClientDocument passsed to SimpleClient.");
355 if (cdocument == null)
357 "Client Error - updateDocument() called before getClientDocument() on this SimpleClient instance.");
358 if (newdoc != cdocument)
360 "Client Error - SimpleClient.updateDocument() can only take the IClientDocument instance returned from SimpleClient.getClientDocument()");
362 if (evgen.isDocumentWatchEnabled())
364 "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.");
365 if (cdocument.isInvalidModification()) {
367 .info("Client has corrupted the vamsas document. It will not be written back to the session - sorry.");
368 // TODO: modify updateDocument signature: We should really raise an
369 // exception here to tell the client that it broke the datamodel.
371 // actually try to write - if necessary.
372 if (!cdocument.isModified()) {
373 // client document is silently got rid of, with no session update
375 if (log.isDebugEnabled())
376 log.debug("updateDocument for " + session.getSessionUrn()
377 + " with unmodified IClientDocument (skipping the write)");
379 writeSessionDocument();
382 // release locks, reset and start to receive events again
383 tidyAwaySessionDocumentState();
387 * garbage collect the ClientDocument instance and re-enable watchers.
389 protected void tidyAwaySessionDocumentState() {
391 log.debug("Finalizing ClientDocument instance.");
392 cdocument.finalize();
393 } catch (Throwable e) {
394 log.error("Exception when trying to garbage collect ClientDocument for "
395 + session.getSessionUrn(), e);
397 cdocument = null; // this is probably done by finalize
400 _session.unlockVamsasDocument();
401 evgen.enableDocumentWatch();
402 } catch (IOException e) {
403 log.warn("IO Problems when releasing lock on session document!", e);
405 .error("IO problems when attempting to release lock on session document.");
410 * write the cdocument instance to the session for real.
412 private void writeSessionDocument() {
414 boolean updated = cdocument.updateSessionDocument();
415 boolean hasContent = cdocument.isModified();
418 .warn("Session document did not update properly for session directory "
419 + _session.sessionDir);
420 // cdocument.archive.cancelArchive(); // LATER: could be done - would
421 // need to prevent updateSessionDocument closing the iohandler.
423 .warn("Session Document updated but may not be valid (false return from org.vamsas.simpleclient.ClientDocument.updateSessionDocument()");
425 log.debug("Document update successful.");
428 _session.setUnsavedFlag();
430 } catch (IOException e) {
431 log.warn("IO Problems when updating document!", e);
432 _session.slog.error("IO problems when attempting to update document.");
439 * @see uk.ac.vamsas.client.IClient#storeDocument(java.io.File)
441 public void storeDocument(File location) {
442 if (location == null)
444 "Vamsas Client API Usage Error: storeDocument called with null location.");
445 log.debug("StoreDocument to " + location);
446 // write storeDocument file to inform other clients that they should raise
447 Lock vamlock = evgen.want_to_store();
448 // Events.DOCUMENT_FINALIZEAPPDATA
450 _session.writeVamsasDocument(location, vamlock);
451 _session.clearUnsavedFlag();
452 } catch (Exception e) {
453 log.warn("Exception whilst trying to store document in " + location, e);
461 * @see uk.ac.vamsas.client.IClient#addVorbaEventHandler(java.lang.String,
462 * java.beans.PropertyChangeListener)
464 public void addVorbaEventHandler(String EventChain, PropertyChangeListener evt) {
465 if (handlers.containsKey(EventChain)) {
466 log.debug("Adding new handler for " + EventChain);
468 ((PropertyChangeSupport) (handler = handlers.get(EventChain)))
469 .addPropertyChangeListener(evt);
470 listeners.add(handler);
471 listeners.add((Object) evt);
478 * @see uk.ac.vamsas.client.IClient#pollUpdate()
480 public void pollUpdate() {
481 log.debug("pollUpdate");
483 log.warn("pollUpdate called on incomplete SimpleClient object.");
487 if (!evgen.isWatcherAlive()) {
488 log.warn("pollUpdate called before joinSession() - trying to do this.");
491 } catch (Exception e) {
492 log.error("Unexpected exception on default call to joinSession", e);
496 // TODO ensure event generator robustly handles these interrupts.
497 log.debug("interrrupting event generator.");
498 evgen.interruptWatching();
499 log.debug("interrrupted event generator.");
505 * @see uk.ac.vamsas.client.IClient#joinSession()
507 public void joinSession() throws Exception {
508 log.debug("Joining Session.");
509 // start the EventGenerator thread.
511 log.warn("joinSession called on incomplete SimpleClient object.");
514 if (evgen.isWatcherAlive())
516 "Join session called twice for the same SimpleClient (IClient instance).");
517 evgen.startWatching();
518 if (evgen.isWatcherAlive())
519 log.debug("Started EventGenerator thread.");
521 log.warn("Failed to start EventGenerator thread.");
523 "Failed to start event generator thread - client cannot be instantiated.");
525 if (evgen.countHandlersFor(Events.DOCUMENT_CREATE) > 0) {
526 // TODO: LATER: is this application connecting to a newly created session
528 // evgen.raise(Events.DOCUMENT_CREATE);
536 * @see uk.ac.vamsas.client.IClient#importDocument(java.io.File)
538 public void importDocument(File location) {
539 // TODO LATER: implement SimpleClient.importDocument()
540 // if (numberOfClients<1 or document file is empty/new, then can import
542 // more complex if data is already present in document. Could have a
543 // 'clearSession' method, too, or dump and overwrite instead.
545 .error("importDocument is not yet implemented for a SimpleClient Session.");
548 * try { this._session.setVamsasDocument(location); } catch (IOException e)
549 * { log.error("importDocument failed."); }
553 public IObjectUpdate getUpdateHandler(Class rootObject) {
554 // TODO Auto-generated method stub
558 public IObjectUpdate[] getUpdateHandlers() {
559 // TODO Auto-generated method stub
563 public void removeUpdateHandler(Class rootObject) {
564 // TODO Auto-generated method stub
568 public void setUpdateHandler(IObjectUpdate handler) {
569 // TODO Auto-generated method stub
574 * retrieves the current VamsasSession to which belong the client
576 * @return the _session
578 protected VamsasSession get_session() {
579 return this._session;
582 SimplePickManager pickmanager = null;
587 * @see uk.ac.vamsas.client.IClient#getPickManager()
589 public IPickManager getPickManager() {
594 private void createPickManager() {
595 if (pickmanager == null) {
596 // TODO: Construct PickManager for session using details from sessionURN!
597 log.debug("Creating PickManager (not from sessionURN yet)");
598 pickmanager = new SimplePickManager(
599 new uk.ac.vamsas.client.picking.SocketManager());
603 protected void releaseActiveClientFile() throws IOException {
605 log.debug("Releasing active client locks");
606 if (activeClientFilelock != null) {// Release the lock
607 log.debug("Releasing lock on active client lock file");
608 activeClientFilelock.release();
610 .debug("ReleaseActiveClientFile called when client has no lock on its clientLockFile");
611 activeClientFilelock = null;
614 .debug("ReleaseActiveClientFile called when client has no lock on its clientLockFile");
616 if (this.clientlockFile != null) {
617 log.debug("trying to delete active client lock file");
618 if (this.clientlockFile.exists()) {
619 this.clientlockFile.delete();
620 log.debug("deleted active client lock file");
624 .debug("ReleaseActiveClientFile called when client has no clientLockFile");
628 protected void createActiveClientFile() throws IOException {
629 if (this.clientlockFile != null)
631 log.debug("createActiveClientFile");
632 // create, if need, subdirectory to contain client files
633 File clientlockFileDir = new File(this.get_session().sessionDir, this
634 .get_session().clientFileDirectory);
635 if (!clientlockFileDir.exists()) {// the directory does not exist, create it
636 if (!clientlockFileDir.mkdirs()) {
637 throw new IOException(
638 "Failed to create sub directory to session directory for client lock files'"
639 + clientlockFileDir.getAbsolutePath() + "'");
642 if (!(clientlockFileDir.isDirectory() && clientlockFileDir.canWrite())) {
643 throw new IOException(
644 "Directory for client lock files is not a directory or is not accessibl: '"
645 + clientlockFileDir.getAbsolutePath() + "'");
648 this.clientlockFile = new File(clientlockFileDir, this.getClientHandle()
649 .getClientUrn().replaceAll("[:;/\\\\]+", ""));
651 log.debug("Creating active client lock file "
652 + this.clientlockFile.getAbsolutePath());
653 Lock clientLock = uk.ac.vamsas.client.simpleclient.LockFactory.getLock(
654 clientlockFile, false);
655 if (clientLock == null || !clientLock.isLocked()) {
657 .fatal("IMPLEMENTATION ERROR: Couldn't get a lock for the client lock file "
660 activeClientFilelock = clientLock;
664 * @return the clientlockFile
666 protected File getClientlockFile() {
667 return clientlockFile;
672 * @return the lock for the client in the session
674 protected Lock getClientLock() {
675 return activeClientFilelock;
678 SimpleClientConfig _config = null;
680 public SimpleClientConfig getSimpleClientConfig() {
681 if (_config == null) {
682 _config = new SimpleClientConfig();