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.InvalidSessionDocumentException;
29 import uk.ac.vamsas.client.InvalidSessionUrnException;
30 import uk.ac.vamsas.client.SessionHandle;
31 import uk.ac.vamsas.client.UserHandle;
32 import uk.ac.vamsas.client.picking.IPickManager;
33 import uk.ac.vamsas.objects.core.Entry;
34 import uk.ac.vamsas.objects.core.VamsasDocument;
35 import uk.ac.vamsas.objects.utils.ProvenanceStuff;
40 public class SimpleClient implements IClient {
42 private static Log log = LogFactory.getLog(SimpleClient.class);
44 protected UserHandle user = null;
46 protected SessionUrn session = null;
47 protected VamsasSession _session;
48 protected ClientHandle client = null;
49 protected EventGeneratorThread evgen = null;
50 protected ClientDocument cdocument = null;
55 private Lock activeClientFilelock = null;
56 private File clientlockFile = null;
59 * object hash table that persists in each client holding vorbaIds and hash values after a document write
61 protected Hashtable extantobjects=null;
63 * construct a transient IdFactory instance - this should last only as long as the
64 * SimpleClient object holds the lock on the vamsas document being created/manipulated.
67 private IdFactory makeVorbaIdFactory() {
68 if (extantobjects==null)
69 extantobjects=new Hashtable();
70 return new IdFactory(getSessionHandle(), client, user, extantobjects);
74 * construct SimpleClient for user, client and VamsasSession directory
75 * use the SimpleClientFactory rather than this constructor directly.
80 protected SimpleClient(UserHandle user, ClientHandle client, VamsasSession sess) throws InvalidSessionUrnException {
81 // TODO: validate user/client/session
86 log.debug("Creating new session for "+_session);
87 session = new SessionUrn(_session);
88 log.debug("Creating new Event Generator");
89 evgen = new EventGeneratorThread(_session, this, handlers);
90 /*} catch (MalformedURLException e) {
91 log.error("Couldn't form a valid SessionUrn object!",e);
92 throw new InvalidSessionUrnException(_session.toString());
94 log.debug("SimpleClient constructed for session "+session.getSessionUrn());
98 * construct new SimpleClientsession by importing objects from an existing vamsas document
102 * @param importingArchive
103 * @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 overwrite session document with file: "+importingArchive);
110 // TODO: write provenance entry for new session indicating the import.
117 * LATER: check that build substitution variables are correct
118 * @see uk.ac.vamsas.client.IClient#getAbout()
120 public String getAbout() {
121 return new String("VORBA SimpleClient version $version$ build $build$");
127 * @see uk.ac.vamsas.client.IClient#getSessionUrn()
129 public String getSessionUrn() {
130 return session.getSessionUrn();
136 * @see uk.ac.vamsas.client.IClient#getSessionHandle()
138 public SessionHandle getSessionHandle() {
139 // TODO: eliminate SessionHandle ? need to refactor interfaces.
140 SessionHandle sh = new SessionHandle(session.getSessionUrn());
147 * @see uk.ac.vamsas.client.IClient#getClientHandle()
149 public ClientHandle getClientHandle() {
156 * @see uk.ac.vamsas.client.IClient#getUserHandle()
158 public UserHandle getUserHandle() {
163 * @return user field for a provenance entry
165 protected String getProvenanceUser() {
166 return new String(user.getFullName());
169 * construct a provenance entry for this client with the specified action string.
171 * @return properly completed provenance entry
173 protected Entry getProvenanceEntry(String action) {
174 Entry prov = ProvenanceStuff.newProvenanceEntry(client.getClientUrn(), getProvenanceUser(), action);
177 private Hashtable handlers = initHandlers();
179 private Vector listeners = new Vector();
182 * make all the PropertyChangeSupport objects for the
183 * events described in uk.ac.vamsas.client.Event
186 private static Hashtable initHandlers() {
187 Hashtable events = new Hashtable();
188 java.util.Iterator evt = Events.EventList.iterator();
189 while (evt.hasNext()) {
190 Object ths = evt.next();
191 events.put(ths, (Object) new PropertyChangeSupport(ths));
199 * @see uk.ac.vamsas.client.IClient#addDocumentUpdateHandler(java.util.EventListener)
201 public void addDocumentUpdateHandler(PropertyChangeListener evt) {
202 this.addVorbaEventHandler(Events.DOCUMENT_UPDATE, evt);
204 boolean finalized=false;
205 private void haltPickmanager() {
206 if (pickmanager!=null) {
207 final SimpleClient dying=this;
210 SimpleClient.log.debug("Stopping pickManager..");
211 dying.pickmanager.shutdown();
212 SimpleClient.log.debug("pickManager halted.");
220 * @see uk.ac.vamsas.client.IClient#finalizeClient()
222 public void finalizeClient() {
224 throw new Error("VAMSAS Client Implementation Error: Finalized called twice for same client instance.");
226 // mark this instance as finalized
229 // TODO: determine if this is last client in session
231 // TODO: raise events like : ((lst_client && document.request.to.close), (client_finalization), (
232 evgen._raise(Events.CLIENT_FINALIZATION, null, this,null);
233 // if (handlers.containsKey(Events.))
234 // if (handlers.containsKey(Events.CLIENT_FINALIZATION))
235 // deregister listeners.
236 log.debug("Stopping pickManager");
239 log.debug("Deregistering Client");
240 _session.removeClient(this);
241 //log.debug("Stopping EventGenerator..");
242 //evgen.stopWatching();
243 this.cdocument = null;
244 SimpleClient.log.debug("EventGenerator halted.");
245 log.debug("finalization Complete.");
251 * @see uk.ac.vamsas.client.IClient#getClientDocument()
253 public IClientDocument getClientDocument() throws IOException {
254 log.debug("getClientDocument");
255 if (cdocument!=null) {
256 // cdocument is non-nill if the ClientDocument.finalise() method hasn't been called.
259 evgen.disableDocumentWatch();
260 VamsasArchive va = null;
262 // LATER: bail out if it takes too long to get the lock ?
263 va = _session.getVamsasDocument();
265 catch (IOException e) {
266 throw new IOException("Failed to get lock on session document");
268 VamsasDocument doc=null;
269 IdFactory vorba = null;
270 // TODO: LATER: reduce size of vorba ids generated from these parameters to IdFactory (mainly sessionHandle rationalization ?)
272 va.setVorba(vorba=makeVorbaIdFactory());
273 // if session currently holds data - read it in - or get a dummy
274 log.debug("Accessing document");
276 va.getVamsasDocument(getProvenanceUser(),
277 "created new session document.", null);
279 log.debug("Successfully retrieved document.");
281 log.error("Unexpectedly retrieved null document!");
283 catch (Exception e) {
284 log.error("Failed to get session document for session directory '"+_session.sessionDir+"'", e);
285 throw new IOException("Failed to get session document for session directory '"+_session.sessionDir+"'");
287 // Construct the IClientDocument instance
289 cdocument = new ClientDocument(doc, va, vorba, this);
295 * @throws Errors for invalid newdoc parameter
296 * @see uk.ac.vamsas.client.IClient#updateDocument(uk.ac.vamsas.client.IClientDocument)
298 public void updateDocument(IClientDocument newdoc) {
299 log.debug("updateDocument:");
300 // Check validity of simpleclient instance and that it holds a lock on the session's document
301 if (!(newdoc instanceof ClientDocument)) {
302 throw new Error("Invalid IClientDocument passsed to SimpleClient.");
305 throw new Error("Client Error - updateDocument() called before getClientDocument() on this SimpleClient instance.");
306 if (newdoc!=cdocument)
307 throw new Error("Client Error - SimpleClient.updateDocument() can only take the IClientDocument instance returned from SimpleClient.getClientDocument()");
309 if (evgen.isDocumentWatchEnabled())
310 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.");
311 if (cdocument.isInvalidModification())
313 log.info("Client has corrupted the vamsas document. It will not be written back to the session - sorry.");
314 // TODO: modify updateDocument signature: We should really raise an exception here to tell the client that it broke the datamodel.
316 // actually try to write - if necessary.
317 if (!cdocument.isModified()) {
318 // client document is silently got rid of, with no session update events.
319 if (log.isDebugEnabled())
320 log.debug("updateDocument for "+session.getSessionUrn()+" with unmodified IClientDocument (skipping the write)");
322 writeSessionDocument();
325 // release locks, reset and start to receive events again
326 tidyAwaySessionDocumentState();
329 * garbage collect the ClientDocument instance and re-enable watchers.
331 protected void tidyAwaySessionDocumentState() {
333 log.debug("Finalizing ClientDocument instance.");
334 cdocument.finalize();
335 } catch (Throwable e) {
336 log.error("Exception when trying to garbage collect ClientDocument for "+session.getSessionUrn(), e);
338 cdocument = null; // this is probably done by finalize
341 _session.unlockVamsasDocument();
342 evgen.enableDocumentWatch();
343 } catch (IOException e) {
344 log.warn("IO Problems when releasing lock on session document!",e);
345 _session.slog.error("IO problems when attempting to release lock on session document.");
350 * write the cdocument instance to the session for real.
352 private void writeSessionDocument() {
354 boolean updated=cdocument.updateSessionDocument();
355 boolean hasContent=cdocument.isModified();
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.");
365 _session.setUnsavedFlag();
368 catch (IOException e) {
369 log.warn("IO Problems when updating document!",e);
370 _session.slog.error("IO problems when attempting to update document.");
377 * @see uk.ac.vamsas.client.IClient#storeDocument(java.io.File)
379 public void storeDocument(File location) {
381 throw new Error("Vamsas Client API Usage Error: storeDocument called with null location.");
382 log.debug("StoreDocument to "+location);
383 // write storeDocument file to inform other clients that they should raise
384 Lock vamlock = evgen.want_to_store();
385 // Events.DOCUMENT_FINALIZEAPPDATA
387 _session.writeVamsasDocument(location, vamlock);
388 _session.clearUnsavedFlag();
389 } catch (Exception e) {
390 log.warn("Exception whilst trying to store document in "+location,e);
398 * @see uk.ac.vamsas.client.IClient#addVorbaEventHandler(java.lang.String,
399 * java.beans.PropertyChangeListener)
401 public void addVorbaEventHandler(String EventChain, PropertyChangeListener evt) {
402 if (handlers.containsKey(EventChain)) {
403 log.debug("Adding new handler for "+EventChain);
405 ((PropertyChangeSupport) (handler = handlers.get(EventChain)))
406 .addPropertyChangeListener(evt);
407 listeners.add(handler);
408 listeners.add((Object) evt);
413 * @see uk.ac.vamsas.client.IClient#pollUpdate()
415 public void pollUpdate() {
416 log.debug("pollUpdate");
418 log.warn("pollUpdate called on incomplete SimpleClient object.");
422 if (!evgen.isWatcherAlive()) {
423 log.warn("pollUpdate called before joinSession() - trying to do this.");
426 } catch (Exception e) {
427 log.error("Unexpected exception on default call to joinSession",e);
431 //TODO ensure event generator robustly handles these interrupts.
432 log.debug("interrrupting event generator.");
433 evgen.interruptWatching();
434 log.debug("interrrupted event generator.");
438 * @see uk.ac.vamsas.client.IClient#joinSession()
440 public void joinSession() throws Exception {
441 log.debug("Joining Session.");
442 // start the EventGenerator thread.
444 log.warn("joinSession called on incomplete SimpleClient object.");
447 if (evgen.isWatcherAlive())
448 throw new Error("Join session called twice for the same SimpleClient (IClient instance).");
449 evgen.startWatching();
450 if (evgen.isWatcherAlive())
451 log.debug("Started EventGenerator thread.");
453 log.warn("Failed to start EventGenerator thread.");
454 throw new Exception("Failed to start event generator thread - client cannot be instantiated.");
456 if (evgen.countHandlersFor(Events.DOCUMENT_CREATE)>0) {
457 //TODO: LATER: is this application connecting to a newly created session document ?
458 //evgen.raise(Events.DOCUMENT_CREATE);
466 * @see uk.ac.vamsas.client.IClient#importDocument(java.io.File)
468 public void importDocument(File location) {
469 // TODO LATER: implement SimpleClient.importDocument()
470 // if (numberOfClients<1 or document file is empty/new, then can import directly.)
471 // more complex if data is already present in document. Could have a 'clearSession' method, too, or dump and overwrite instead.
472 log.error("importDocument is not yet implemented for a SimpleClient Session.");
475 this._session.setVamsasDocument(location);
476 } catch (IOException e) {
477 log.error("importDocument failed.");
481 public IObjectUpdate getUpdateHandler(Class rootObject) {
482 // TODO Auto-generated method stub
486 public IObjectUpdate[] getUpdateHandlers() {
487 // TODO Auto-generated method stub
491 public void removeUpdateHandler(Class rootObject) {
492 // TODO Auto-generated method stub
496 public void setUpdateHandler(IObjectUpdate handler) {
497 // TODO Auto-generated method stub
502 * retrieves the current VamsasSession to which belong the client
503 * @return the _session
505 protected VamsasSession get_session() {
506 return this._session;
508 SimplePickManager pickmanager=null;
510 * @see uk.ac.vamsas.client.IClient#getPickManager()
512 public IPickManager getPickManager() {
517 private void createPickManager() {
518 if (pickmanager==null){
519 // TODO: Construct PickManager for session using details from sessionURN!
520 log.debug("Creating PickManager (not from sessionURN yet)");
521 pickmanager = new SimplePickManager(new uk.ac.vamsas.client.picking.SocketManager());
526 protected void releaseActiveClientFile() throws IOException
529 log.debug("Releasing active client locks");
530 if( activeClientFilelock != null)
532 log.debug("Releasing lock on active client lock file");
533 activeClientFilelock.release();
534 log.debug("ReleaseActiveClientFile called when client has no lock on its clientLockFile");
535 activeClientFilelock = null;
537 log.debug("ReleaseActiveClientFile called when client has no lock on its clientLockFile");
539 if (this.clientlockFile != null)
541 log.debug("trying to delete active client lock file");
542 if (this.clientlockFile.exists())
544 this.clientlockFile.delete();
545 log.debug("deleted active client lock file");
548 log.debug("ReleaseActiveClientFile called when client has no clientLockFile");
552 protected void createActiveClientFile() throws IOException
554 if(this.clientlockFile != null )return;
555 log.debug("createActiveClientFile");
556 //create, if need, subdirectory to contain client files
557 File clientlockFileDir = new File ( this.get_session().sessionDir, this.get_session().clientFileDirectory);
558 if( !clientlockFileDir.exists())
559 {//the directory does not exist, create it
560 if (! clientlockFileDir.mkdirs())
562 throw new IOException("Failed to create sub directory to session directory for client lock files'"+clientlockFileDir.getAbsolutePath()+"'");
567 if (!(clientlockFileDir.isDirectory() && clientlockFileDir.canWrite()))
569 throw new IOException("Directory for client lock files is not a directory or is not accessibl: '"+clientlockFileDir.getAbsolutePath()+"'");
572 this.clientlockFile = new File (clientlockFileDir, this.getClientHandle().getClientUrn().replaceAll("[:;/\\\\]+",""));
574 log.debug("Creating active client lock file "+ this.clientlockFile.getAbsolutePath());
575 Lock clientLock = uk.ac.vamsas.client.simpleclient.LockFactory.getLock(clientlockFile, false);
576 if (clientLock==null || !clientLock.isLocked())
578 log.fatal("IMPLEMENTATION ERROR: Couldn't get a lock for the client lock file "+clientlockFile);
580 activeClientFilelock = clientLock;
583 * @return the clientlockFile
585 protected File getClientlockFile() {
586 return clientlockFile;
590 * @return the lock for the client in the session
592 protected Lock getClientLock() {
593 return activeClientFilelock;
595 SimpleClientConfig _config = null;
596 public SimpleClientConfig getSimpleClientConfig() {
599 _config = new SimpleClientConfig();