/* * This file is part of the Vamsas Client version 0.1. * Copyright 2009 by Jim Procter, Iain Milne, Pierre Marguerite, * Andrew Waterhouse and Dominik Lindner. * * Earlier versions have also been incorporated into Jalview version 2.4 * since 2008, and TOPALi version 2 since 2007. * * The Vamsas Client is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The Vamsas Client is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the Vamsas Client. If not, see . */ package uk.ac.vamsas.client.simpleclient; import java.io.IOException; import java.util.Vector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import uk.ac.vamsas.client.ClientHandle; import uk.ac.vamsas.client.IClientAppdata; import uk.ac.vamsas.client.IClientDocument; import uk.ac.vamsas.client.UserHandle; import uk.ac.vamsas.client.Vobject; import uk.ac.vamsas.client.VorbaId; import uk.ac.vamsas.objects.core.ApplicationData; import uk.ac.vamsas.objects.core.User; import uk.ac.vamsas.objects.core.VAMSAS; import uk.ac.vamsas.objects.core.VamsasDocument; import uk.ac.vamsas.objects.utils.AppDataReference; import uk.ac.vamsas.test.objects.Core; /** * Maintains a collection of vamsas objects, appdatas and states, and provides * api for a SimpleClient's client. TODO: test and migrate * ArchiveClient.getAppData methods to here and retest in ExampleApplication * * @author jimp */ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implements IClientDocument { private static Log log = LogFactory.getLog(ClientDocument.class); private VamsasDocument doc; protected SimpleClient sclient; protected VamsasArchive iohandler = null; /** * indicate if new data has been incorporated */ private boolean isModified = false; /** * Public method for internal use by SimpleClient. * * @return true if document has been modified. */ public boolean isModified() { return isModified; } /** * * prepare Application-side dataset from the vamsas Document iohandler * * @param doc * - the dataset * @param docHandler * - the sessionFile IO handler * @param Factory * - the source of current and new vorbaIds * @param sclient * - the simpleclient instance */ protected ClientDocument(VamsasDocument doc, VamsasArchive docHandler, IdFactory Factory, SimpleClient sclient) { super(Factory.getVorbaIdHash(), Factory); /** * prepare Application-side dataset from the vamsas Document iohandler */ this.sclient = sclient; iohandler = docHandler; this.doc = doc; _VamsasRoots = doc.getVAMSAS(); } /* * (non-Javadoc) * * @see * uk.ac.vamsas.client.IClientDocument#getObject(uk.ac.vamsas.client.VorbaId) */ public Vobject getObject(VorbaId id) { if (vamsasObjects == null) { log.debug("getObject called on null objrefs list."); return null; } if (vamsasObjects.containsKey(id)) return (Vobject) vamsasObjects.get(id); log.debug("Returning null Vobject reference for id " + id.getId()); return null; } /* * (non-Javadoc) * * @see * uk.ac.vamsas.client.IClientDocument#getObjects(uk.ac.vamsas.client.VorbaId * []) */ public Vobject[] getObjects(VorbaId[] ids) { if (vamsasObjects == null) { log.debug("getObject[] called on null vamsasObjects list."); return null; } Vobject[] vo = new Vobject[ids.length]; for (int i = 0, j = ids.length; i < j; i++) if (vamsasObjects.containsKey(ids[i])) vo[i] = (Vobject) vamsasObjects.get(ids[i]); else log.debug("Returning null Vobject reference for id " + ids[i].getId()); return vo; } /** * internal reference to single copy of Document Roots array */ private VAMSAS[] _VamsasRoots = null; /** * set if the client has corrupted the Vamsas Document structure somehow. if * this is set the document will never be written back to the session unless * the corruption is fixed. */ private boolean invalidModification = false; protected void updateDocumentRoots() { if (doc == null) { log .error("updateDocumentRoots called on null document. Probably an implementation error."); return; } if (isModified) { if (_VamsasRoots != null) { doc.setVAMSAS(_VamsasRoots); _VamsasRoots = null; } } } /* * (non-Javadoc) LATER: currently there is only one Vector of roots ever * passed to client - decide if this is correct (means this is not thread safe * and may behave unexpectedly) * * @see uk.ac.vamsas.client.IClientDocument#getVamsasRoots() */ public VAMSAS[] getVamsasRoots() { if (doc == null) { log.debug("Null document for getVamsasRoots(), returning null"); return null; } if (iohandler == null) { // LATER: decide on read-only status of ClientDocument object log.warn("getVamsasRoots() called on possibly read-only document."); } if (_VamsasRoots != null) return _VamsasRoots; VAMSAS[] roots = doc.getVAMSAS(); if (roots == null) { // Make a new one to return to client to get filled. _VamsasRoots = new VAMSAS[] { new VAMSAS() }; registerObject(_VamsasRoots[0]); // Do provenance now. just in case. doc.getProvenance().addEntry( sclient.getProvenanceEntry("Created new document root [id=" + _VamsasRoots[0].getId() + "]")); doc.addVAMSAS(_VamsasRoots[0]); } else { _VamsasRoots = new VAMSAS[roots.length]; for (int r = 0; r < roots.length; r++) _VamsasRoots[r] = roots[r]; } return _VamsasRoots; } private int _contains(VAMSAS root, VAMSAS[] docRoots) { if (root == null) return -1; if (docRoots == null || docRoots.length == 0) return -1; VorbaId d_id = null, r_id = root.getVorbaId(); for (int i = 0, j = docRoots.length; i < j; i++) if (docRoots[i] == root || (docRoots[i] != null && (d_id = docRoots[i].getVorbaId()) != null) && d_id.equals(r_id)) return i; return -1; } /** * verify that newr version is really an intact version of the * * @param newVersion * (may be modified) * @param oldVersion * @param modflag * @return true if newVersion is a valid root that preserves original * references */ private boolean isValidUpdate(VAMSAS newVersion, final VAMSAS oldVersion, ClientDocument modflag) { // ideal - this cascades down the two structures, ensuring that all ID'd // objects in one are present in the other. if (oldVersion == newVersion) { // may be a virgin root element. if (!newVersion.isRegistered()) { _registerObject(newVersion); // TODO: check - this call hasn't been // tested. (seems to work now) modflag.isModified = true; } // TODO: Should attempt to repair document if client app has // deleted/broken bits of it if (oldVersion.is__stored_in_document()) { // retrieve compare hashCodes to detect update. if (oldVersion.get__last_hash() != oldVersion.hashCode()) { log.debug("Modified hashcode for vamsas root " + oldVersion.getVorbaId()); modflag.isModified = true; } else { log.debug("Unmodified vamsas root " + oldVersion.getVorbaId()); } } // just do internal validation for moment. try { if (getSimpleClientConfig().validateUpdatedRoots()) { newVersion.validate(); } return true; } catch (Exception e) { log.error("Validation Exception for new vamsas root :" + newVersion.getVorbaId(), e); modflag.invalidModification = true; } return false; } else { // redundant ? if (oldVersion.is__stored_in_document()) if (!newVersion.isRegistered()) { _registerObject(newVersion); modflag.isModified = true; } try { if (getSimpleClientConfig().validateMergedRoots()) { newVersion.validate(); } modflag.isModified = true; return true; } catch (Exception e) { log.error("Validation Exception for new vamsas root :" + newVersion.getVorbaId(), e); } } return false; /** * LATER: MUCH LATER! - not needed for simple case and this routine * shouldn't live in this class anymore isValidUpdate : Ideally. we * efficiently walk down, comparing hashes, to deal with merging and * verifying provenance for objects * * // extract root objects if (newroots != null) { // check newroots for * objects that were present in the old document // check to see if the * 'old' objects have been modified // if they have ? we overwrite them with * their new version, ensuring that // provenance is updated. // if they * haven't ? do nothing ? * * for (int i = 0, k = newroots.length; i < k; i++) { if * (newroots[i].isRegistered()) { // easy - just check if anything has * changed and do provenance Vobject oldversion = * getObject(newroots[i].getVorbaId()); if (oldversion instanceof VAMSAS) { * // LATER: appropriate merging behaviour when two clients have improperly * modified the same Vobject independently. if (newroots[i].get__last_hash() * != newroots[i].hashCode()) { // client has modified this Vobject since * last retrieval. if (newroots[i].get__last_hash() != * oldversion.get__last_hash()) { // Vobject has been modified by another * client since this // client's // last access to document. } } } else { * throw new Error( * "SimpleClient error when using setVamsasRoots : The vorbaId for Vobject " * + i + * " does not refer to an Vobject of type VAMSAS in the current document!"); * } } else { if (!newroots[i].is__stored_in_document()) { // check if * Vobject is modified if (newroots[i].get__last_hash() != * newroots[i].hashCode()) { // it is - so we add newroots[i] as a new * Vobject, with updated // provenance. } else { // do nothing newroots[i] = * null; } } else { // just add newroots[i] as a new Vobject in the document * // - with appropriate provenance. } } } */ } private SimpleClientConfig getSimpleClientConfig() { return sclient.getSimpleClientConfig(); } /** * merge old and new root vectors * * @param newr * This array may be written to * @param original * @param the * client document (usually this) which this root set belongs to. * @return merged vector of vamsas roots */ private VAMSAS[] _combineRoots(VAMSAS[] newr, final VAMSAS[] original, ClientDocument modflag) { Vector rts = new Vector(); for (int i = 0, j = original.length; i < j; i++) { int k = _contains(original[i], newr); if (k > -1) { if (isValidUpdate(newr[k], original[i], modflag)) { // set by isValidUpdate if the hashcodes were really different from // last store rts.add(newr[k]); newr[k] = null; } else { // LATER: try harder to merge ducument roots. log.warn("Couldn't merge new VAMSAS root " + newr[k].getId()); newr[k] = null; // LATER: this means we ignore mangled roots. NOT GOOD } } else { // add in order. rts.add(original[i]); } } // add remaining (new) roots for (int i = 0, j = newr.length; i < j; i++) { if (newr[i] != null) { rts.add(newr[i]); modflag.isModified = true; } } newr = new VAMSAS[rts.size()]; for (int i = 0, j = rts.size(); i < j; i++) newr[i] = (VAMSAS) rts.get(i); return newr; } /** * update the document with new roots. LATER: decide: this affects the next * call to getVamsasRoots() * * @see org.vamsas.IClientDocument.setVamsasRoots */ public void setVamsasRoots(VAMSAS[] newroots) { if (doc == null) { log.debug("setVamsasRoots called on null document."); return; } VAMSAS[] newr; if (newroots == null) { log.debug("setVamsasRoots(null) - do nothing."); return; } // are we dealing with same array ? if (_VamsasRoots != newroots) { // merge roots into local version. newr = new VAMSAS[newroots.length]; for (int i = 0; i < newr.length; i++) newr[i] = newroots[i]; newr = _combineRoots(newr, _VamsasRoots, this); } else { newr = new VAMSAS[_VamsasRoots.length]; for (int i = 0; i < newr.length; i++) newr[i] = _VamsasRoots[i]; } // actually compare with document root set for final combination (to ensure // nothing is lost) _VamsasRoots = _combineRoots(newr, doc.getVAMSAS(), this); } /* * (non-Javadoc) LATER: decide: this affects the next call to getVamsasRoots() * * @see * uk.ac.vamsas.client.IClientDocument#addVamsasRoot(uk.ac.vamsas.objects. * core.VAMSAS) */ public void addVamsasRoot(VAMSAS newroot) { if (doc == null) { log.debug("addVamsasRoots called on null document."); return; } VAMSAS[] newroots = _combineRoots(new VAMSAS[] { newroot }, getVamsasRoots(), this); _VamsasRoots = newroots; } /* * (non-Javadoc) * * @see * uk.ac.vamsas.client.IClientDocument#registerObjects(uk.ac.vamsas.client * .Vobject[]) */ public VorbaId[] registerObjects(Vobject[] unregistered) { if (doc == null) { log.warn("registerObjects[] called on null document."); return null; } if (vamsasObjects == null) { log.warn("registerObjects[] called for null vamsasObjects hasharray."); return null; } if (unregistered != null) { VorbaId ids[] = new VorbaId[unregistered.length]; for (int i = 0, k = unregistered.length; i < k; i++) if (unregistered[i] != null) { log.warn("Null Vobject passed to registerObject[] at position " + i); return null; } else { ids[i] = registerObject(unregistered[i]); } log.debug("Registered " + unregistered.length + " objects - total of " + vamsasObjects.size() + " ids."); return ids; } return null; } /* * (non-Javadoc) * * @see * uk.ac.vamsas.client.IClientDocument#registerObject(uk.ac.vamsas.client. * Vobject) */ public VorbaId registerObject(Vobject unregistered) { if (doc == null) { log.warn("registerObjects called on null document."); return null; } if (vamsasObjects == null) { log.warn("registerObjects called for null vamsasObjects hasharray."); return null; } if (iohandler == null) { log.warn("registerObjects called for read only document."); return null; } if (unregistered != null) { VorbaId id = _registerObject(unregistered); log.debug("Registered object - total of " + vamsasObjects.size() + " ids."); return id; } log.warn("Null Vobject passed to registerObject."); return null; } /** * IClientAppdata instance - if it exists. */ SimpleClientAppdata scappd = null; /* * (non-Javadoc) * * @see uk.ac.vamsas.client.IClientDocument#getClientAppdata() */ public IClientAppdata getClientAppdata() { // TODO: getClientAppdata not tested in ArchiveClient mockup log.error("TODO: TEST Client Appdata access methods"); if (doc == null) { log.warn("getClientAppdata called on null document."); return null; } if (scappd == null) { log.debug("Creating new SimpleClientAppdata instance for " + sclient.getSessionHandle()); scappd = new SimpleClientAppdata(this); if (scappd == null) { // LATER: may not need this as a warning message. log.warn("Null appdata object for " + sclient.getSessionHandle()); } else { log.debug("Created SimpleClientAppdata successfully."); } } else { log.debug("Returning existing SimpleClientAppdata reference."); } return scappd; } /** * access the vamsas document * * @return the session's vamsas document */ protected VamsasDocument getVamsasDocument() { return doc; } /** * returns the read-only IO interface for the vamsas document Jar file * * @return */ protected VamsasArchiveReader getVamsasArchiveReader() { if (iohandler == null) { log .error("Near fatal. Null VamsasArchive iohandler so can't get VamsasArchiveReader"); return null; } try { log.info("TODO: test getVamsasArchiveReader"); return iohandler.getOriginalArchiveReader(); } catch (Exception e) { log.warn("Unable to create OriginalArchiveReader!", e); } return null; } /** * called by vamsas api to write updated document to session * * @return true if update was successful * @throws java.io.IOException */ protected boolean updateSessionDocument() throws java.io.IOException { boolean docupdate = true; // 'non-serious' problems below set this false if (doc == null) { log.warn("updateSessionDocument called on null document."); throw new java.io.IOException("Document is closed."); } if (iohandler == null) { log .warn("updateSessionDocument called on null document iohandler handler."); throw new java.io.IOException("Document is closed."); } if (!isModified() && !scappd.isModified()) { log.debug("Document update not necessary. returning false."); return false; } VamsasSession session = sclient._session; log.debug("updating Session Document in " + session.sessionDir); // update the VamsasDocument structure with any new appData's. // try to update the sessionFile log .debug("Attempting to update session " + sclient.session.getSessionUrn()); if (scappd != null && scappd.isModified()) { ClientHandle client = sclient.client; UserHandle user = sclient.user; scappd.closeForWriting(); if (scappd.appsGlobal == null) { log.debug("Creating new appData entry for this application..."); // first write for this application - add a new section in document ApplicationData appd = scappd.appsGlobal = new ApplicationData(); appd.setName(client.getClientName()); // appd.setUrn(client.getClientUrn()); appd.setVersion(client.getVersion()); doc.addApplicationData(appd); // embed or jarEntry ? - for now only jarEntry's are dealt with. appd.setDataReference(AppDataReference.uniqueAppDataReference(doc, sclient.client.getClientUrn())); log.debug("... created."); } if (scappd.newAppData != null && scappd.newAppData.sessionFile.exists()) { log.debug("Beginning update for new Global Appdata..."); // new global appdata to write. if (scappd.appsGlobal.getData() != null) { scappd.appsGlobal.setData(null); scappd.appsGlobal.setDataReference(AppDataReference .uniqueAppDataReference(doc, sclient.client.getClientUrn())); } // LATER: use a switch to decide if the data should be written as a // reference or as an embedded data chunk scappd.updateAnAppdataEntry(iohandler, scappd.appsGlobal, scappd.newAppData); log.debug("...Successfully updated Global Appdata Entry."); } if (scappd.newUserData != null && scappd.newUserData.sessionFile.exists()) { log.debug("Beginning to update Users Appdata entry...."); if (scappd.usersData == null) { // create new user appdata scappd.usersData = new User(); scappd.usersData.setFullname(user.getFullName()); scappd.usersData.setOrganization(user.getOrganization()); scappd.appsGlobal.addUser(scappd.usersData); } User appd = scappd.usersData; if (appd.getData() != null || appd.getDataReference() == null) { // LATER make standard appDataReference constructor for client+user appd.setData(null); String safe_username = user.getFullName(); int t = safe_username.indexOf(" "); if (t != -1) { safe_username = safe_username.substring(t); } appd.setDataReference(AppDataReference.uniqueAppDataReference(doc, sclient.client.getClientUrn() + safe_username)); } scappd.updateAnAppdataEntry(iohandler, scappd.usersData, scappd.newUserData); log.debug("...Successfully updated user AppData entry."); } } try { if (iohandler.transferRemainingAppDatas()) log.debug("Remaining appdatas were transferred."); else log.debug("No remaining appdatas were transferred. (Correct?)"); } catch (Exception e) { log.error("While transferring remaining AppDatas", e); } log.debug("Updating Document..."); // now update the document. - this was basically the doUpdate method in // test.ArchiveClient updateDocumentRoots(); try { iohandler.putVamsasDocument(doc); log.debug("Successfully written document entry."); } catch (Exception e) { log.error("Marshalling error for vamsas document.", e); docupdate = false; // pass on the (probable) object validation error } iohandler.closeArchive(); iohandler = null; // so this method cannot be called again for this instance log.debug("...successully finished and closed."); return docupdate; // no errors ? } /* * (non-Javadoc) * * @see java.lang.Object#finalize() */ protected void finalize() throws Throwable { log.debug("Garbage collecting on ClientDocument instance."); if (scappd != null) { scappd.finalize(); scappd = null; } if (doc != null) { doc = null; } // disengage from client if (sclient != null && sclient.cdocument == this) sclient.cdocument = null; sclient = null; super.finalize(); } public Vector getUpdatedObjects() { // TODO: WALK through the document objects calling the update mechanism for // each one, or just pass this vector back to client ?return updatedObjects; return null; } /** * if this is set the document will never be written back to the session * unless the corruption is fixed. * * @return the invalidModification */ public boolean isInvalidModification() { return invalidModification; } /** * set if the client has corrupted the Vamsas Document structure somehow. if * this is set the document will never be written back to the session unless * the corruption is fixed. * * @param invalidModification * the invalidModification to set */ public void setInvalidModification(boolean invalidModification) { this.invalidModification = invalidModification; } }