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 java.nio.channels.FileLock activeClientFilelock = null;
56 private FileChannel activeClientFileChannel = null;
57 private File clientlockFile = null;
60 * object hash table that persists in each client holding vorbaIds and hash values after a document write
62 protected Hashtable extantobjects=null;
64 * construct a transient IdFactory instance - this should last only as long as the
65 * SimpleClient object holds the lock on the vamsas document being created/manipulated.
68 private IdFactory makeVorbaIdFactory() {
69 if (extantobjects==null)
70 extantobjects=new Hashtable();
71 return new IdFactory(getSessionHandle(), client, user, extantobjects);
75 * construct SimpleClient for user, client and VamsasSession directory
76 * use the SimpleClientFactory rather than this constructor directly.
81 protected SimpleClient(UserHandle user, ClientHandle client, VamsasSession sess) throws InvalidSessionUrnException {
82 // TODO: validate user/client/session
87 log.debug("Creating new session for "+_session);
88 session = new SessionUrn(_session);
89 log.debug("Creating new Event Generator");
90 evgen = new EventGeneratorThread(_session, this, handlers);
91 /*} catch (MalformedURLException e) {
92 log.error("Couldn't form a valid SessionUrn object!",e);
93 throw new InvalidSessionUrnException(_session.toString());
95 log.debug("SimpleClient constructed for session "+session.getSessionUrn());
99 * construct new session by importing objects from an existing vamsas document
103 * @param importingArchive
104 * @throws Exception IOExceptions for Session IO problems, and general Exception if importing document is invalid.
106 protected SimpleClient(UserHandle user, ClientHandle client, VamsasSession sess, File importingArchive) throws Exception {
107 this(user, client, sess);
108 VamsasArchive sessdoc = _session.getVamsasDocument();
110 VamsasArchiveReader odoc = new VamsasArchiveReader(importingArchive);
111 SimpleDocument sdoc = new SimpleDocument(makeVorbaIdFactory());
112 VamsasDocument doc = sdoc.getVamsasDocument(odoc);
113 sessdoc.putVamsasDocument(doc, sdoc.vorba);
114 sessdoc.closeArchive();
115 log.debug("Imported new vamsas data from "+importingArchive);
116 } catch (Exception e) {
117 sessdoc.cancelArchive();
118 // write a dummy iohandler
119 _session.slog.info("Exception when importing document data from "+importingArchive);
120 log.warn("While importing session data from existing archive in "+importingArchive, e);
121 throw new Exception("Failed to import data from "+importingArchive, e);
127 * LATER: check that build substitution variables are correct
128 * @see uk.ac.vamsas.client.IClient#getAbout()
130 public String getAbout() {
131 return new String("VORBA SimpleClient version $version$ build $build$");
137 * @see uk.ac.vamsas.client.IClient#getSessionUrn()
139 public String getSessionUrn() {
140 return session.getSessionUrn();
146 * @see uk.ac.vamsas.client.IClient#getSessionHandle()
148 public SessionHandle getSessionHandle() {
149 // TODO: eliminate SessionHandle ? need to refactor interfaces.
150 SessionHandle sh = new SessionHandle(session.getSessionUrn());
157 * @see uk.ac.vamsas.client.IClient#getClientHandle()
159 public ClientHandle getClientHandle() {
166 * @see uk.ac.vamsas.client.IClient#getUserHandle()
168 public UserHandle getUserHandle() {
173 * @return user field for a provenance entry
175 protected String getProvenanceUser() {
176 return new String(user.getFullName());
179 * construct a provenance entry for this client with the specified action string.
181 * @return properly completed provenance entry
183 protected Entry getProvenanceEntry(String action) {
184 Entry prov = ProvenanceStuff.newProvenanceEntry(client.getClientUrn(), getProvenanceUser(), action);
187 private Hashtable handlers = initHandlers();
189 private Vector listeners = new Vector();
192 * make all the PropertyChangeSupport objects for the
193 * events described in uk.ac.vamsas.client.Event
196 private static Hashtable initHandlers() {
197 Hashtable events = new Hashtable();
198 java.util.Iterator evt = Events.EventList.iterator();
199 while (evt.hasNext()) {
200 Object ths = evt.next();
201 events.put(ths, (Object) new PropertyChangeSupport(ths));
209 * @see uk.ac.vamsas.client.IClient#addDocumentUpdateHandler(java.util.EventListener)
211 public void addDocumentUpdateHandler(PropertyChangeListener evt) {
212 this.addVorbaEventHandler(Events.DOCUMENT_UPDATE, evt);
214 boolean finalized=false;
215 private void haltPickmanager() {
216 if (pickmanager!=null) {
217 final SimpleClient dying=this;
220 SimpleClient.log.debug("Stopping pickManager..");
221 dying.pickmanager.shutdown();
222 SimpleClient.log.debug("pickManager halted.");
230 * @see uk.ac.vamsas.client.IClient#finalizeClient()
232 public void finalizeClient() {
234 throw new Error("VAMSAS Client Implementation Error: Finalized called twice for same client instance.");
236 // mark this instance as finalized
239 // TODO: determine if this is last client in session
241 // TODO: raise events like : ((lst_client && document.request.to.close), (client_finalization), (
242 evgen._raise(Events.CLIENT_FINALIZATION, null, this,null);
243 // if (handlers.containsKey(Events.))
244 // if (handlers.containsKey(Events.CLIENT_FINALIZATION))
245 // deregister listeners.
246 log.debug("Stopping pickManager");
249 log.debug("Deregistering Client");
250 _session.removeClient(this);
251 //log.debug("Stopping EventGenerator..");
252 //evgen.stopWatching();
253 SimpleClient.log.debug("EventGenerator halted.");
254 this.cdocument = null;
255 log.debug("finalization Complete.");
261 * @see uk.ac.vamsas.client.IClient#getClientDocument()
263 public IClientDocument getClientDocument() throws IOException {
264 log.debug("getClientDocument");
265 if (cdocument!=null) {
266 // cdocument is non-nill if the ClientDocument.finalise() method hasn't been called.
269 evgen.disableDocumentWatch();
270 VamsasArchive va = null;
272 // LATER: bail out if it takes too long to get the lock ?
273 va = _session.getVamsasDocument();
275 catch (IOException e) {
276 throw new IOException("Failed to get lock on session document");
278 VamsasDocument doc=null;
279 IdFactory vorba = null;
280 // TODO: LATER: reduce size of vorba ids generated from these parameters to IdFactory (mainly sessionHandle rationalization ?)
282 va.setVorba(vorba=makeVorbaIdFactory());
283 // if session currently holds data - read it in - or get a dummy
284 log.debug("Accessing document");
286 va.getVamsasDocument(getProvenanceUser(),
287 "created new session document.", null);
289 log.debug("Successfully retrieved document.");
291 log.error("Unexpectedly retrieved null document!");
293 catch (Exception e) {
294 log.error("Failed to get session document for session directory '"+_session.sessionDir+"'", e);
295 throw new IOException("Failed to get session document for session directory '"+_session.sessionDir+"'");
297 // Construct the IClientDocument instance
299 cdocument = new ClientDocument(doc, va, vorba, this);
305 * @throws Errors for invalid newdoc parameter
306 * @see uk.ac.vamsas.client.IClient#updateDocument(uk.ac.vamsas.client.IClientDocument)
308 public void updateDocument(IClientDocument newdoc) {
309 log.debug("updateDocument:");
310 // Check validity of simpleclient instance and that it holds a lock on the session's document
311 if (!(newdoc instanceof ClientDocument)) {
312 throw new Error("Invalid IClientDocument passsed to SimpleClient.");
315 throw new Error("Client Error - updateDocument() called before getClientDocument() on this SimpleClient instance.");
316 if (newdoc!=cdocument)
317 throw new Error("Client Error - SimpleClient.updateDocument() can only take the IClientDocument instance returned from SimpleClient.getClientDocument()");
319 if (evgen.isDocumentWatchEnabled())
320 throw new Error("Client Error - or Library Bug : Document watcher still enabled whilst ClientDocument instance exists.");
322 if (!cdocument.isModified()) {
323 // client document is silently got rid of, with no session update events.
324 if (log.isDebugEnabled())
325 log.debug("updateDocument for "+session.getSessionUrn()+" with unmodified IClientDocument (skipping the write)");
328 boolean updated=cdocument.updateSessionDocument();
330 log.warn("Session document did not update properly for session directory "+_session.sessionDir);
331 // cdocument.archive.cancelArchive(); // LATER: could be done - would need to prevent updateSessionDocument closing the iohandler.
332 _session.slog.warn("Session Document updated but may not be valid (false return from org.vamsas.simpleclient.ClientDocument.updateSessionDocument()");
334 log.debug("Document update successful.");
336 _session.setUnsavedFlag();
338 catch (IOException e) {
339 log.warn("IO Problems when updating document!",e);
340 _session.slog.error("IO problems when attempting to update document.");
343 // garbage collect the ClientDocument instance.
345 log.debug("Finalizing ClientDocument instance.");
346 cdocument.finalize();
347 } catch (Throwable e) {
348 log.error("Exception when trying to garbage collect ClientDocument for "+session.getSessionUrn(), e);
350 cdocument = null; // this is probably done by finalize
353 _session.unlockVamsasDocument();
354 evgen.enableDocumentWatch();
355 } catch (IOException e) {
356 log.warn("IO Problems when releasing lock on session document!",e);
357 _session.slog.error("IO problems when attempting to release lock on session document.");
364 * @see uk.ac.vamsas.client.IClient#storeDocument(java.io.File)
366 public void storeDocument(File location) {
368 throw new Error("Vamsas Client API Usage Error: storeDocument called with null location.");
369 log.debug("StoreDocument to "+location);
370 // write storeDocument file to inform other clients that they should raise
371 Lock vamlock = evgen.want_to_store();
372 // Events.DOCUMENT_FINALIZEAPPDATA
374 _session.writeVamsasDocument(location, vamlock);
375 _session.clearUnsavedFlag();
376 } catch (Exception e) {
377 log.warn("Exception whilst trying to store document in "+location,e);
385 * @see uk.ac.vamsas.client.IClient#addVorbaEventHandler(java.lang.String,
386 * java.beans.PropertyChangeListener)
388 public void addVorbaEventHandler(String EventChain, PropertyChangeListener evt) {
389 if (handlers.containsKey(EventChain)) {
390 log.debug("Adding new handler for "+EventChain);
392 ((PropertyChangeSupport) (handler = handlers.get(EventChain)))
393 .addPropertyChangeListener(evt);
394 listeners.add(handler);
395 listeners.add((Object) evt);
400 * @see uk.ac.vamsas.client.IClient#pollUpdate()
402 public void pollUpdate() {
403 log.debug("pollUpdate");
405 log.warn("pollUpdate called on incomplete SimpleClient object.");
409 if (!evgen.isWatcherAlive()) {
410 log.warn("pollUpdate called before joinSession() - trying to do this.");
413 } catch (Exception e) {
414 log.error("Unexpected exception on default call to joinSession",e);
418 //TODO ensure event generator robustly handles these interrupts.
419 log.debug("interrrupting event generator.");
420 evgen.interruptWatching();
421 log.debug("interrrupted event generator.");
425 * @see uk.ac.vamsas.client.IClient#joinSession()
427 public void joinSession() throws Exception {
428 log.debug("Joining Session.");
429 // start the EventGenerator thread.
431 log.warn("joinSession called on incomplete SimpleClient object.");
434 if (evgen.isWatcherAlive())
435 throw new Error("Join session called twice for the same SimpleClient (IClient instance).");
436 evgen.startWatching();
437 if (evgen.isWatcherAlive())
438 log.debug("Started EventGenerator thread.");
440 log.warn("Failed to start EventGenerator thread.");
441 throw new Exception("Failed to start event generator thread - client cannot be instantiated.");
443 if (evgen.countHandlersFor(Events.DOCUMENT_CREATE)>0) {
444 //TODO: LATER: is this application connecting to a newly created session document ?
445 //evgen.raise(Events.DOCUMENT_CREATE);
453 * @see uk.ac.vamsas.client.IClient#importDocument(java.io.File)
455 public void importDocument(File location) {
456 // TODO LATER: implement SimpleClient.importDocument()
457 // if (numberOfClients<1 or document file is empty/new, then can import directly.)
458 // more complex if data is already present in document. Could have a 'clearSession' method, too, or dump and overwrite instead.
459 log.error("importDocument is not yet implemented for a SimpleClient Session.");
462 this._session.setVamsasDocument(location);
463 } catch (IOException e) {
464 log.error("importDocument failed.");
468 public IObjectUpdate getUpdateHandler(Class rootObject) {
469 // TODO Auto-generated method stub
473 public IObjectUpdate[] getUpdateHandlers() {
474 // TODO Auto-generated method stub
478 public void removeUpdateHandler(Class rootObject) {
479 // TODO Auto-generated method stub
483 public void setUpdateHandler(IObjectUpdate handler) {
484 // TODO Auto-generated method stub
489 * retrieves the current VamsasSession to which belong the client
490 * @return the _session
492 protected VamsasSession get_session() {
493 return this._session;
495 SimplePickManager pickmanager=null;
497 * @see uk.ac.vamsas.client.IClient#getPickManager()
499 public IPickManager getPickManager() {
504 private void createPickManager() {
505 if (pickmanager==null){
506 // TODO: Construct PickManager for session using details from sessionURN!
507 log.debug("Creating PickManager (not from sessionURN yet)");
508 pickmanager = new SimplePickManager(new uk.ac.vamsas.client.picking.SocketManager());
513 protected void releaseActiveClientFile() throws IOException
516 log.debug("Releasing active client file");
517 if( activeClientFilelock != null)
519 activeClientFilelock.release();
521 if (activeClientFileChannel != null)
523 activeClientFileChannel.close();
524 if (this.clientlockFile != null)
526 this.clientlockFile.delete();
527 log.debug("deleted active client lock file");
532 protected void createActiveClientFile() throws IOException
534 if(this.clientlockFile != null )return;
535 log.debug("createActiveClientFile");
536 //create, if need, subdirectory to contain client files
537 File clientlockFileDir = new File ( this.get_session().sessionDir, this.get_session().clientFileDirectory);
538 if( !clientlockFileDir.exists())
539 {//the directory does not exist, create it
540 if (! clientlockFileDir.mkdirs())
542 throw new IOException("Failed to create sub directory to session directory for client lock files'"+clientlockFileDir.getAbsolutePath()+"'");
547 if (!(clientlockFileDir.isDirectory() && clientlockFileDir.canWrite()))
549 throw new IOException("Directory for client lock files is not a directory or is not accessibl: '"+clientlockFileDir.getAbsolutePath()+"'");
552 this.clientlockFile = new File (clientlockFileDir, this.getClientHandle().getClientUrn().replaceAll("[:;/\\\\]+",""));
554 log.debug("Creating active client lock file "+ this.clientlockFile.getAbsolutePath());
555 if (clientlockFile.exists())
556 {//should not happen, file should be deleted when the application closes or when a crashed application has been detected
557 log.error("client lock file already exits");
560 //create the empty file
561 if(! clientlockFile.createNewFile())
563 log.error("Unable to create active client lock file");
568 log.debug("file created");
569 // Get a file channel for the file
570 FileChannel channel = new RandomAccessFile(clientlockFile, "rw").getChannel();
572 // Use the file channel to create a lock on the file.
573 // This method blocks until it can retrieve the lock.
574 java.nio.channels.FileLock activeClientFilelock ;// = channel.lock();
576 // Try acquiring the lock without blocking. This method returns
577 // null or throws an exception if the file is already locked.
580 activeClientFilelock = channel.tryLock();
581 log.debug("Got lock");
583 catch (OverlappingFileLockException e)
585 // File is already locked in this thread or virtual machine
586 log.error("Oups the file is already locked",e);
595 } catch (Exception e) {
596 log.error("Error during lock file creation",e);
604 * @return the clientlockFile
606 protected File getClientlockFile() {
607 return clientlockFile;