added simple semaphore file mechanism for session directory.
[vamsas.git] / src / org / vamsas / client / object.java
index fc13b4f..18054b7 100644 (file)
@@ -3,6 +3,18 @@
  */
 package org.vamsas.client;
 
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.exolab.castor.mapping.FieldDescriptor;
+import org.exolab.castor.mapping.FieldHandler;
+import org.exolab.castor.xml.util.XMLClassDescriptorImpl;
+import org.vamsas.test.simpleclient.VamsasArchive;
+
 /**
  * Base class for all Vamsas objects extracted from an IClientDocument. An
  * object maybe registered or unregistered.
@@ -11,19 +23,32 @@ package org.vamsas.client;
  * 
  */
 public abstract class object {
-
+  static Log log = LogFactory.getLog(object.class);
+  
   /**
-   * unique id for all vamsas objects allows unambiguous referencing to any
-   * object in the vamsas document
+   * true if object was stored in a vamsas Document or has been retrieved from it
    */
   protected boolean __stored_in_document = false;
+  /**
+   * memory of the last doHash() value computed for the object 
+   * @see doHash()
+   */
+  protected long __last_hash = 0; 
+  /**
+   * set by testInstanceForIdField() if object should have a VorbaId
+   */
+  protected boolean registerable = false; 
 
-  protected long __last_hash = 0;
-
-  protected boolean registerable = false;
 
+  /**
+   * unique id for all vamsas objects allows unambiguous referencing to any
+   * object in the vamsas document
+   */
   protected VorbaId vorbaId = null;
 
+  /**
+   * the source of unique VorbaIds.
+   */
   protected IVorbaIdFactory __vorba = null;
 
   /**
@@ -35,16 +60,15 @@ public abstract class object {
   }
 
   /**
-   * set the isRegisterable flag based on the presence of a 'String id' field in
+   * set the isRegisterable flag based on the presence of a 'private String _id' field in
    * the reflected class instance.
    */
   private void testInstanceForIdField() {
     // look for the id field (should be an NCName string)
-    // TODO: decide if 'id' is an appropriate reserved attribute name for the
-    // VorbaId
+    // TODO: decide if 'id' is an appropriate reserved attribute name for the VorbaId
     try {
-      java.lang.reflect.Field fd = this.getClass().getField("id");
-      if (fd.getType().getClass().equals("astring".getClass())) {
+      java.lang.reflect.Field fd = this.getClass().getDeclaredField("_id");
+      if (String.class.isAssignableFrom(fd.getType())) {
         this.setRegisterable(true);
       }
     } catch (SecurityException e) {
@@ -55,35 +79,45 @@ public abstract class object {
   }
 
   /**
-   * update the object instance's String id field, based on the contents of the
+   * update the object instance's _id field, based on the contents of the
    * VorbaId. Only call this if you mean to do it!
    */
   protected void setInstanceIdField() {
     if (registerable) {
-      if (vorbaId!=null) 
+      if (__vorba != null)
         try {
-          java.lang.reflect.Field fd = this.getClass().getField("id");
-          fd.set((Object) this, (Object) new String(vorbaId.id));
-        } catch (IllegalAccessException e) {
-          System.err.println("SourceGeneration of "+this.getClass().toString()
-              +"\n has resulted in an inaccessible 'id' field!\nCannot set ID from the vorbaId object.")
-              e.printStackTrace(System.err);
+          Method fd = this.getClass().getMethod("setId", new Class[] { String.class });
+          fd.invoke((Object) this, new Object[] {new String(this.getVorbaId().id)});
+          log.debug(this.getClass().getName()+" called setInstanceIdField!");
+        } catch (InvocationTargetException e) { 
+          log.error("SourceGeneration of "
+              + this.getClass().toString()
+              + "\n has resulted in an inaccessible 'setId' method!\nCannot set ID from the vorbaId object.", e);
         }
-        catch (SecurityException e) {
-          e.printStackTrace(System.err);
-        } catch (NoSuchFieldException e) {
+        catch (IllegalAccessException e) {
+          log.error("SourceGeneration of "
+                  + this.getClass().toString()
+                  + "\n has resulted in an inaccessible 'setId' method!\nCannot set ID from the vorbaId object.", e);
+        } catch (SecurityException e) {
+          log.error("Security access violation for "+this.getClass().toString(),e);
+        } catch (NoSuchMethodException e) {
+          log.warn(this.getClass().toString()+" was erroneously marked as a Vorba object class (Implementation error?)");
           this.setRegisterable(false);
         }
     } else {
       System.err.println("Client error. Trying to setInstanceIdField on a "
-          +this.getClass().toString()+" (which cannot be given a vorbaId)");
+          + this.getClass().toString() + " (which cannot be given a vorbaId)");
     }
   }
+
   /**
    * calculate a hash for the object with all housekeeping fields at standard
    * values. (isRegisterable is an immutable attribute property)
+   * TODO: decide if __stored_in_document should be included in the hash or not.
+   * @return true if new hash different to last hash
    */
-  synchronized protected void doHash() {
+  synchronized protected boolean doHash() {
+    long __old_hash = __last_hash;
     __last_hash = 0;
     VorbaId thisid = vorbaId;
     IVorbaIdFactory factory = __vorba;
@@ -94,6 +128,21 @@ public abstract class object {
     vorbaId = thisid;
     __vorba = factory;
     __stored_in_document = stored;
+    return (__old_hash==0) || (__old_hash == __last_hash);
+  }
+
+  /**
+   * TODO: combine two versions of the same collection object to resolve
+   * asynchronous updates to the same vamsas object Merges two vamsas objects,
+   * one of which is a later version of the earlier (ie they have the same
+   * vorbaId but one is a later version recently read from the vamsasDocument
+   * collection.
+   * 
+   * @return
+   */
+  protected boolean merge(object laterCopy) {
+    log.warn(this.getClass().getName()+".merge() not implemented.");
+    return true;
   }
 
   /**
@@ -114,7 +163,7 @@ public abstract class object {
     if (registerable && vorbaId == null) {
       // Try to use the associated factory.
       if (__vorba != null)
-        if ((vorbaId = __vorba.makeVorbaId()) == null)
+        if ((vorbaId = __vorba.makeVorbaId(this)) == null)
           return null; // Factory not valid.
         else {
           this.setInstanceIdField();
@@ -142,9 +191,10 @@ public abstract class object {
   /**
    * for use by Vorba agent to reflect state of vamsas object to client
    * application.
-   * 
-   * @param __stored_in_document
-   *          The __stored_in_document to set.
+   * Setting stored_in_document on a registerable object without a 
+   * vorbaId will mean is will *never* get a vorbaId and 
+   * horrible things will happen.
+   * @param __stored_in_document true if object has been marshalled into current document.
    */
   protected void set__stored_in_document(boolean __stored_in_document) {
     this.__stored_in_document = __stored_in_document;
@@ -162,20 +212,126 @@ public abstract class object {
   }
 
   /**
-   * @return Returns the registerable.
+   * @return true if object can have a vorbaId
    */
   public boolean isRegisterable() {
     return registerable;
   }
 
   /**
-   * Called during unmarshalling - if object has an id string, it is
-   * registerable.
-   * 
-   * @param registerable
-   *          The registerable to set.
+   * Called by __testInstanceForidField and the post-unmarshalling handler
+   * to indicate if object will have a vorbaId.
+   * @param registerable 
    */
   protected void setRegisterable(boolean registerable) {
     this.registerable = registerable;
   }
+  /**
+   * ensure's internal id field corresponds to vorbaId and
+   * cascade through all fields referring to an instance of object
+   * calling the same method on them.
+   * TODO: LATER: properly apply castors own field mechanisms to get at accessors
+   * TODO: FIX CYCLIC __ensure+instance_ids
+   * Implementation note for the todo:
+   * this works like a depth-first search over all vamsas objects in an vamsasDocument. 
+   * The doHash() function is used as the 'visited' flag - 
+   * this *is not* a valid heuristic, although it will work "most of the time".
+   * TODO: LATER? Add another method for setDefaultProvenanceField (in the spirit of setInstanceIdField) using the info from the __vorba.getClient/User/Session methods 
+   */
+  protected void __ensure_instance_ids() {
+    if (__vorba==null)
+      throw new Error("Improperly intialised org.vamsas.client.object - no VorbaFactory given.");
+    log.debug("doing "+this.getClass()+".__ensure_instance_ids()");
+    if (!__stored_in_document && registerable)
+      setInstanceIdField();
+    if (!doHash())
+      return; // nothing has changed in this object - probably visited it before.
+    Class descriptor = null;
+    XMLClassDescriptorImpl descimpl = null;
+    try {
+      // castor descriptor resolver magic
+      descriptor = this.getClass().getClassLoader().loadClass(this.getClass().getName()+"Descriptor");
+      descimpl = (XMLClassDescriptorImpl) descriptor.getConstructor(null).newInstance(null);
+    } catch (Exception e) {
+      log.fatal("Source Generation Error!: Couldn't resolve descriptor for "
+          +this.getClass().getName()
+          +" was 'generate descriptors' set for castorbuilder.properties?");
+      return;
+    }
+    FieldDescriptor fields[] = descimpl.getFields();
+    for (int i=0,j=fields.length; i<j; i++) {
+      Class type= fields[i].getFieldType();
+      if (type.isArray()) {
+        if (object[].class.isAssignableFrom(type)) {
+          try {
+            Object val = fields[i].getHandler().getValue(this);
+            if (val!=null) {
+              object vals[] = (object[]) val; 
+              for (int k=0, l=vals.length; k<l; k++) {
+                if (vals[k].__vorba==null)
+                  vals[k].__vorba = __vorba; // propagate IVorbaIdFactory
+                vals[k].__ensure_instance_ids();
+              }
+            }
+          }
+          catch (Exception e) {
+            log.error("Client error - could not access array "+type.getName()+" in "+this.getClass().getName(), e);
+          }
+        }
+      } else
+        if (object.class.isAssignableFrom(type)) {
+          try {
+            FieldHandler fh = fields[i].getHandler();
+            object rf = null;
+            if (fh != null) {
+              Object fval = fh.getValue(this);
+              if (fval!=null) {
+                if (fval.getClass().isArray()) {
+                  //if (object[].class.isAssignableFrom(type)) {
+                    try {
+                      object vals[] = (object[]) fval; 
+                      for (int k=0, l=vals.length; k<l; k++) {
+                        if (vals[k].__vorba==null)
+                          vals[k].__vorba = __vorba; // propagate IVorbaIdFactory
+                        vals[k].__ensure_instance_ids();
+                      }
+                    }
+                    catch (Exception e) {
+                      log.error("Client error - could not access (fhval)array "+type.getName()+" in "+this.getClass().getName(), e);
+                    }
+                  //}
+                } else {
+                  rf = (object) fval;
+                  log.debug("Got value for "+fields[i].getFieldName());
+                }
+              }
+            } else {
+              // fuck around, fuck around, jump up jump up and get down! */
+              Object o = fields[i].getClassDescriptor();
+              if (o!=null) {
+                // XMLClassDescriptorImpl fclasdes = (XMLClassDescriptorImpl) o;
+                String methname = "get"+fields[i].getFieldName();
+                Method fgetmeth = this.getClass().getMethod(methname,null);
+                if (fgetmeth!=null) {
+                  Object fval = fgetmeth.invoke(this,null);
+                  if (fval!=null)
+                    rf = (object) fval;
+                } else {
+                  log.warn("Couldn't find "+this.getClass().getName()+"."+methname);
+                }
+              }
+            }
+            if (rf!=null) {
+              if (rf.__vorba==null)
+                rf.__vorba = __vorba; // propagate IVorbaIdFactory
+              rf.__ensure_instance_ids();
+            }
+          }
+          catch (Exception e) {
+            log.error("Client error - could not access "+type.getName()+" in "+this.getClass().getName(), e);
+          }
+        }
+    }
+    
+  }
 }