made SimpleClientConfig externally configurable and added mechanism to prevent invali...
[vamsas.git] / src / uk / ac / vamsas / client / simpleclient / ClientDocument.java
index 86294be..b9e918e 100644 (file)
@@ -8,12 +8,6 @@ import java.util.Vector;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.vamsas.objects.core.ApplicationData;
-import org.vamsas.objects.core.User;
-import org.vamsas.objects.core.VAMSAS;
-import org.vamsas.objects.core.VamsasDocument;
-import org.vamsas.objects.utils.AppDataReference;
-import org.vamsas.test.objects.Core;
 
 import uk.ac.vamsas.client.ClientHandle;
 import uk.ac.vamsas.client.IClientAppdata;
@@ -21,16 +15,24 @@ 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.
+ * 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 archive = null;
+  protected VamsasArchive iohandler = null;
   /**
    * indicate if new data has been incorporated
    */
@@ -42,10 +44,9 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
   public boolean isModified() {
     return isModified;
   }
-  private Vector updatedObjects=null;
   /**
    *
-   *  prepare Application-side dataset from the vamsas Document archive
+   *  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
@@ -56,12 +57,12 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
     
     
     /**
-     * prepare Application-side dataset from the vamsas Document archive
+     * prepare Application-side dataset from the vamsas Document iohandler
      */
     this.sclient = sclient;
-    archive = docHandler;
+    iohandler = docHandler;
     this.doc = doc;
-    updatedObjects=null;  /// TODO: correct this line
+    _VamsasRoots=doc.getVAMSAS();
   }
   
   /*
@@ -102,7 +103,25 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
    * 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()
@@ -112,7 +131,7 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
       log.debug("Null document for getVamsasRoots(), returning null");
       return null;
     }
-    if (archive==null) {
+    if (iohandler==null) {
       // LATER: decide on read-only status of ClientDocument object
       log.warn("getVamsasRoots() called on possibly read-only document.");
     }
@@ -122,6 +141,7 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
     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]);
@@ -138,39 +158,78 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
       return -1;
     if (docRoots==null || docRoots.length==0)
       return -1;
-    String r_id = root.getId();
+    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 && docRoots[i].getId().equals(r_id)))
+      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) {
+  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);
-      // Should retrieve original version and compare - unless local hashes can be used to determine if resultSet has been truncated.
+      {
+        _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.
-      if (newVersion.isValid())
+      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);
-      if (newVersion.isValid())
+        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 isValidUpdate : Ideally. we efficiently walk down, comparing hashes, to deal with merging and verifying provenance for objects
+     * 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) {
@@ -217,6 +276,9 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
         }
       }*/
   }
+  private SimpleClientConfig getSimpleClientConfig() {
+    return sclient.getSimpleClientConfig();
+}
   /**
    * merge old and new root vectors
    * @param newr This array may be written to
@@ -226,12 +288,11 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
    */
   private VAMSAS[] _combineRoots(VAMSAS[] newr, final VAMSAS[] original, ClientDocument modflag) {
     Vector rts = new Vector();
-    boolean modified=false;
     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])) {
-          modified=true;
+        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 {
@@ -248,14 +309,12 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
     for (int i=0,j=newr.length; i<j; i++) {
       if (newr[i]!=null) {
         rts.add(newr[i]);
-        modified=true;
+        modflag.isModified=true;
       }
     }
     newr = new VAMSAS[rts.size()];
     for (int i=0,j=rts.size(); i<j; i++)
       newr[i] = (VAMSAS) rts.get(i);
-    if (modflag!=null)
-      modflag.isModified = modified;
     return newr;
   }
   
@@ -293,14 +352,14 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
   
   /* (non-Javadoc)
    * LATER: decide: this affects the next call to getVamsasRoots()
-   * @see uk.ac.vamsas.client.IClientDocument#addVamsasRoot(org.vamsas.objects.core.VAMSAS)
+   * @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}, _VamsasRoots, this);
+    VAMSAS[] newroots = _combineRoots(new VAMSAS[] {newroot}, getVamsasRoots(), this);
     _VamsasRoots = newroots;  
   }
   
@@ -344,6 +403,11 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
       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.");
@@ -360,6 +424,8 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
    * @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;
@@ -390,21 +456,31 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
    * @return
    */
   protected VamsasArchiveReader getVamsasArchiveReader() {
+    if (iohandler==null) {
+      log.error("Near fatal. Null VamsasArchive iohandler so can't get VamsasArchiveReader");
+      return null;
+    }
     try {
-      return archive.getOriginalArchiveReader();
+      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 (archive==null) {
-      log.warn("updateSessionDocument called document archive handler.");
+    if (iohandler==null) {
+      log.warn("updateSessionDocument called on null document iohandler handler.");
       throw new java.io.IOException("Document is closed.");
     }
     
@@ -417,7 +493,7 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
     // 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.isModified()) {
+    if (scappd!=null && scappd.isModified()) {
       ClientHandle client = sclient.client;
       UserHandle user = sclient.user;
       scappd.closeForWriting();      
@@ -441,7 +517,7 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
           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(archive, scappd.appsGlobal, scappd.newAppData);
+        scappd.updateAnAppdataEntry(iohandler, scappd.appsGlobal, scappd.newAppData);
         log.debug("...Successfully updated Global Appdata Entry.");
       }
       if (scappd.newUserData!=null && scappd.newUserData.sessionFile.exists()) {
@@ -464,25 +540,31 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
           }
           appd.setDataReference(AppDataReference.uniqueAppDataReference(doc, sclient.client.getClientUrn()+safe_username));
         }
-        scappd.updateAnAppdataEntry(archive, scappd.usersData, scappd.newUserData);
+        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.
+    // now update the document. - this was basically the doUpdate method in test.ArchiveClient
+    updateDocumentRoots();
     try {
-      archive.putVamsasDocument(doc);
+      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 
     }
-    if (archive.transferRemainingAppDatas())
-      log.debug("Remaining appdatas were transferred.");
-    else
-      log.debug("No remaining appdatas were transferred. (Correct?)");
-    archive.closeArchive();
+    iohandler.closeArchive();
+    iohandler=null; // so this method cannot be called again for this instance
     log.debug("...successully finished and closed.");
     return docupdate; // no errors ?
   }
@@ -499,13 +581,29 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
       doc = null;
     }
     // disengage from client
-    if (sclient!=null)
+    if (sclient!=null && sclient.cdocument==this)
       sclient.cdocument = null;
     sclient=null;
     
     super.finalize();
   }
   public Vector getUpdatedObjects() {
-    return updatedObjects;
+    // 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;
   }
 }