update detection mechanism based comparing the difference between the position of...
[vamsas.git] / src / uk / ac / vamsas / client / Vobject.java
1 /**
2  * 
3  */
4 package uk.ac.vamsas.client;
5
6 import java.lang.reflect.Field;
7 import java.lang.reflect.InvocationTargetException;
8 import java.lang.reflect.Method;
9 import java.util.Iterator;
10
11 import org.apache.commons.logging.Log;
12 import org.apache.commons.logging.LogFactory;
13 import org.exolab.castor.mapping.FieldDescriptor;
14 import org.exolab.castor.mapping.FieldHandler;
15 import org.exolab.castor.xml.util.XMLClassDescriptorImpl;
16
17 import uk.ac.vamsas.test.simpleclient.VamsasArchive;
18
19 /**
20  * Base class for all Vamsas objects extracted from an IClientDocument. An
21  * Vobject maybe registered or unregistered.
22  * 
23  * @author jimp
24  * 
25  */
26 public abstract class Vobject {
27   static Log log = LogFactory.getLog(Vobject.class);
28   
29   /**
30    * true if Vobject was stored in a vamsas Document or has been retrieved from it
31    */
32   protected boolean __stored_in_document = false;
33   /**
34    * true if Vobject was updated since the vamsas library last read a Vobj with the same VorbaId from a document.
35    */
36   protected boolean __updated_since_last_read = false;
37   /**
38    * true if Vobject appeared in the document after the last access by this vamsas library instance
39    */
40   protected boolean __added_since_last_read = false;
41   /**
42    * memory of the last doHash() value computed for the Vobject 
43    * @see doHash()
44    */
45   protected int __last_hash = 0; 
46   /**
47    * set by testInstanceForIdField() if Vobject should have a VorbaId
48    */
49   protected boolean registerable = false; 
50   protected boolean __visited = false;
51   /**
52    * reference to containing object for this Vobject.
53    */
54   protected Vobject V_parent=null;
55   /**
56    * unique id for all vamsas objects allows unambiguous referencing to any
57    * Vobject in the vamsas document
58    */
59   protected VorbaId vorbaId = null;
60
61   /**
62    * the source of unique VorbaIds.
63    */
64   protected IVorbaIdFactory __vorba = null;
65   
66   /* (non-Javadoc)
67    * @see java.lang.Object#finalize()
68    */
69   protected void finalize() throws Throwable {
70     V_parent=null;
71     __vorba=null;
72     vorbaId=null;
73     super.finalize();
74   }
75
76   /**
77    * 
78    */
79   protected Vobject() {
80     super();
81     testInstanceForIdField();
82   }
83   java.lang.reflect.Field ___id_field=null; // set to ease pain of reflection
84   /**
85    * set the isRegisterable flag based on the presence of a 'private String _id' field in
86    * the reflected class instance.
87    */
88   private void testInstanceForIdField() {
89     // TODO: decide if 'id' is an appropriate reserved attribute name for the VorbaId
90     // look for the id field in all castor classes (should be an NCName string)
91     
92     Class thisclass=this.getClass();
93     setRegisterable(false);
94     while (!thisclass.equals(Vobject.class)) {
95       try {
96         java.lang.reflect.Field fd = thisclass.getDeclaredField("_id");
97         if (String.class.isAssignableFrom(fd.getType())) {
98           ___id_field=fd;
99           this.setRegisterable(true);
100           break;
101         }
102       } catch (SecurityException e) {
103         log.error("Unexpected Security Exception whilst finding id fields to set!",e);
104       } catch (NoSuchFieldException e) {
105         thisclass=thisclass.getSuperclass();
106       }
107     }
108   }
109   // boolean __testedInstance=false;
110   /**
111    * update the Vobject instance's _id field, based on the contents of the
112    * VorbaId. Only call this if you mean to do it!
113    */
114   protected void setInstanceIdField() {
115     /*if (!registerable && !__testedInstance) {
116       testInstanceForIdField();
117       __testedInstance=true;
118     }*/
119     if (registerable) {
120       if (__vorba != null)
121         try {
122           Method fd = this.getClass().getMethod("setId", new Class[] { String.class });
123           fd.invoke((Object) this, new Object[] {new String(this.getVorbaId().id)});
124           log.debug(this.getClass().getName()+" called setInstanceIdField!");
125         } catch (InvocationTargetException e) { 
126           log.error("SourceGeneration of "
127               + this.getClass().toString()
128               + "\n has resulted in an inaccessible 'setId' method!\nCannot set ID from the vorbaId Vobject.", e);
129         }
130         catch (IllegalAccessException e) {
131           log.error("SourceGeneration of "
132                   + this.getClass().toString()
133                   + "\n has resulted in an inaccessible 'setId' method!\nCannot set ID from the vorbaId Vobject.", e);
134         } catch (SecurityException e) {
135           log.error("Security access violation for "+this.getClass().toString(),e);
136         } catch (NoSuchMethodException e) {
137           log.warn(this.getClass().toString()+" was erroneously marked as a Vorba Vobject class (Implementation error?)");
138           this.setRegisterable(false);
139         }
140     } else {
141       System.err.println("Client error. Trying to setInstanceIdField on a "
142           + this.getClass().toString() + " (which cannot be given a vorbaId)");
143     }
144   }
145   
146   protected String __getInstanceIdField() {
147     /*if (!registerable && !__testedInstance) {
148       testInstanceForIdField();
149       __testedInstance=true;
150     }*/
151     if (registerable) {
152       if (__vorba != null)
153         try {
154           Method fd = this.getClass().getMethod("getId", (Class[]) null);
155           Object idstring = fd.invoke((Object) this, (Object[]) null);
156           log.debug(this.getClass().getName()+" called getInstanceIdField!");
157           if (idstring!=null && idstring instanceof String) {
158             if (((String) idstring).length()>0)
159               return (String) idstring;
160           }
161         } catch (InvocationTargetException e) { 
162           log.error("SourceGeneration of "
163               + this.getClass().toString()
164               + "\n has resulted in an inaccessible 'getId' method!\nCannot set ID from the vorbaId Vobject.", e);
165         }
166         catch (IllegalAccessException e) {
167           log.error("SourceGeneration of "
168                   + this.getClass().toString()
169                   + "\n has resulted in an inaccessible 'getId' method!\nCannot set ID from the vorbaId Vobject.", e);
170         } catch (SecurityException e) {
171           log.error("Security access violation for "+this.getClass().toString(),e);
172         } catch (NoSuchMethodException e) {
173           log.warn(this.getClass().toString()+" was erroneously marked as a Vorba Vobject class (Implementation error?)");
174           this.setRegisterable(false);
175         }
176     } else {
177       System.err.println("Client error. Trying to getInstanceIdField on a "
178           + this.getClass().toString() + " (which cannot be given a vorbaId)");
179     }
180     return null;
181   }
182
183   /**
184    * calculate a hash for the Vobject with all housekeeping fields at standard
185    * values. (isRegisterable is an immutable attribute property)
186    * TODO: LATER: make this hash function compute a hash that truly reflects changes in Vobject attributes for benefit of update mechanism
187    * @return true if new hash different to last hash (or first time its been computed)
188    */
189   synchronized protected boolean doHash() {
190     long __old_hash = __last_hash;
191     __last_hash = 0;
192     Vobject _V_parent=V_parent;
193     V_parent=null;
194     VorbaId thisid = vorbaId;
195     IVorbaIdFactory factory = __vorba;
196     boolean stored = __stored_in_document;
197     boolean updated = __updated_since_last_read;
198     boolean visited = __visited;
199     java.lang.reflect.Field idfield = ___id_field;
200     ___id_field=null;
201     __updated_since_last_read=false;
202     __stored_in_document=false;
203     boolean added_since=__added_since_last_read;
204     __added_since_last_read=false;
205     long l_hash = __l_hash;
206     __l_hash = 0;
207     vorbaId = null;
208     __vorba = null;
209     __visited=false;
210     // compute hash
211     __last_hash = this.hashCode();
212     // reset houseskeeping variables
213     ___id_field=idfield;
214     vorbaId = thisid;
215     __vorba = factory;
216     __stored_in_document = stored;
217     __updated_since_last_read=updated;
218     V_parent=_V_parent;
219     __visited=visited;
220     __added_since_last_read=added_since;
221     __l_hash = l_hash;
222     // return true if first time hash was computed or if hash has changed
223     return (__old_hash==0) || (__old_hash != __last_hash);
224   }
225
226   /**
227    * TODO: combine two versions of the same collection Vobject to resolve
228    * asynchronous updates to the same vamsas Vobject Merges two vamsas objects,
229    * one of which is a later version of the earlier (ie they have the same
230    * vorbaId but one is a later version recently read from the vamsasDocument
231    * collection.
232    * 
233    * @return
234    */
235   protected boolean merge(Vobject laterCopy) {
236     log.warn(this.getClass().getName()+".merge() not implemented.");
237     return true;
238   }
239
240   /**
241    * 
242    * @return true if Vobject is registered
243    */
244   public boolean isRegistered() {
245     return (registerable) ? (vorbaId != null) : false;
246   }
247
248   /**
249    * Method to get fixed reference for the Vobject in the vamsas document.
250    * 
251    * @returns null if Vobject is neither registered or not associated with a
252    *          properly instantiated VorbaIdFactory.
253    */
254   public VorbaId getVorbaId() {
255     if (registerable && vorbaId == null) {
256       if (this.__stored_in_document) {
257         if (__vorba!=null)
258           vorbaId=uk.ac.vamsas.client.VorbaId.newId(this.__getInstanceIdField());
259       }
260       // Try to use the associated factory.
261       if (__vorba != null)
262         if ((vorbaId = __vorba.makeVorbaId(this)) == null)
263           return null; // Factory not valid.
264         else {
265           this.setInstanceIdField();
266           return vorbaId;
267         }
268     }
269     return vorbaId;
270   }
271
272   /**
273    * used by the IClient implementation to generate unique Id based on client
274    * applications current namespace.
275    */
276   protected void setVorbaId(VorbaId newid) {
277     vorbaId = newid;
278   }
279
280   /**
281    * @return true if Vobject is present in Vamsas Document.
282    */
283   public boolean is__stored_in_document() {
284     return __stored_in_document;
285   }
286
287   /**
288    * @return true if this object has been updated in the currently stored document since the last time a Vobject with the same ID was read from a Vamsas Document
289    */
290   public boolean isUpdated() {
291     return __updated_since_last_read;
292   }
293   /**
294    * 
295    * @return true if this object was added to the document after the last time the vamsas library acessed the session document
296    */
297   public boolean isNewInDocument() {
298     return __added_since_last_read;
299   }
300   /**
301    * Set internal flag to indicate this object was updated since the last document read
302    * @param __updated_since_last_read the __updated_since_last_read to set
303    */
304   protected void set__updated_since_last_read(boolean __updated_since_last_read) {
305     this.__updated_since_last_read = __updated_since_last_read;
306     if(__updated_since_last_read && log.isDebugEnabled())
307       log.debug("Registered update for "+this.getVorbaId());
308   }
309
310   /**
311    * for use by Vorba agent to reflect state of vamsas Vobject to client
312    * application.
313    * Setting stored_in_document on a registerable Vobject without a 
314    * vorbaId will mean is will *never* get a vorbaId and 
315    * horrible things will happen.
316    * @param __stored_in_document true if Vobject has been marshalled into current document.
317    */
318   protected void set__stored_in_document(boolean __stored_in_document) {
319     this.__stored_in_document = __stored_in_document;
320   }
321
322   /**
323    * @param __added_since_last_read the __added_since_last_read to set
324    */
325   protected void set__added_since_last_read(boolean __added_since_last_read) {
326     this.__added_since_last_read = __added_since_last_read;
327   }
328
329   /**
330    * __last_hash is the hash value computed when the Vobject was last checked
331    * against a IClientDocument generated by the Vobject's parent IClient
332    * instance.
333    * 
334    * @return Returns the __last_hash.
335    */
336   public int get__last_hash() {
337     return __last_hash;
338   }
339
340   /**
341    * @return true if Vobject can have a vorbaId
342    */
343   public boolean isRegisterable() {
344     return registerable;
345   }
346
347   /**
348    * Called by __testInstanceForidField and the post-unmarshalling handler
349    * to indicate if Vobject will have a vorbaId.
350    * @param registerable 
351    */
352   protected void setRegisterable(boolean registerable) {
353     this.registerable = registerable;
354   }
355   /**
356    * ensure's internal id field corresponds to vorbaId and
357    * cascade through all fields referring to an instance of Vobject
358    * calling the same method on them.
359    * TODO: LATER: properly apply castors own field mechanisms to get at accessors
360    * TODO: FIX CYCLIC __ensure+instance_ids
361    * Implementation note for the todo:
362    * this works like a depth-first search over all vamsas objects in an vamsasDocument. 
363    * __visited is the visited flag, any Vobj who's flag is of a different parity 
364    * to the visited argument will be recursed on. 
365    * note - the doHash() function used to be used as the 'visited' flag - 
366    * this *is not* a valid heuristic, although it will work "most of the time".
367    * TODO: LATER? Add another method for setDefaultProvenanceField (in the spirit of setInstanceIdField) using the info from the __vorba.getClient/User/Session methods 
368    */
369   protected void __ensure_instance_ids() {
370     __ensure_instance_ids(!__visited);
371   }
372   protected void __ensure_instance_ids(boolean visited) {
373     if (__vorba==null)
374       throw new Error("Improperly intialised uk.ac.vamsas.client.Vobject - no VorbaFactory given.");
375     log.debug("doing "+this.getClass()+".__ensure_instance_ids()");
376     if (!__stored_in_document && registerable)
377       setInstanceIdField();
378     if (__visited==visited)
379       return;
380     __visited=visited;
381     //__vorba.updateHashValue(this);
382     
383     Class descriptor = null;
384     XMLClassDescriptorImpl descimpl = null;
385     try {
386       // castor descriptor resolver magic
387       descriptor = this.getClass().getClassLoader().loadClass(this.getClass().getName()+"Descriptor");
388       descimpl = (XMLClassDescriptorImpl) descriptor.getConstructor((Class[])null).newInstance((Object[])null);
389     } catch (Exception e) {
390       log.fatal("Source Generation Error!: Couldn't resolve descriptor for "
391           +this.getClass().getName()
392           +" was 'generate descriptors' set for castorbuilder.properties?");
393       return;
394     }
395     FieldDescriptor fields[] = descimpl.getFields();
396     for (int i=0,j=fields.length; i<j; i++) {
397       Class type= fields[i].getFieldType();
398       if (type.isArray()) {
399         if (Vobject[].class.isAssignableFrom(type)) {
400           try {
401             Object val = fields[i].getHandler().getValue(this);
402             if (val!=null) {
403               Vobject vals[] = (Vobject[]) val; 
404               for (int k=0, l=vals.length; k<l; k++) {
405                 if (vals[k].__vorba==null)
406                   vals[k].__vorba = __vorba; // propagate IVorbaIdFactory
407                 if (vals[k].V_parent==null)
408                   vals[k].V_parent=this; // propagate parent reference to this element.
409                 vals[k].__ensure_instance_ids(visited);
410               }
411             }
412           }
413           catch (Exception e) {
414             log.error("Client error - could not access array "+type.getName()+" in "+this.getClass().getName(), e);
415           }
416         }
417       } else
418         if (Vobject.class.isAssignableFrom(type)) {
419           try {
420             FieldHandler fh = fields[i].getHandler();
421             Vobject rf = null;
422             if (fh != null) {
423               Object fval = fh.getValue(this);
424               if (fval!=null) {
425                 if (fval.getClass().isArray()) {
426                   //if (Vobject[].class.isAssignableFrom(type)) {
427                     try {
428                       Vobject vals[] = (Vobject[]) fval; 
429                       for (int k=0, l=vals.length; k<l; k++) {
430                         if (vals[k].__vorba==null)
431                           vals[k].__vorba = __vorba; // propagate IVorbaIdFactory
432                         if (vals[k].V_parent==null)
433                           vals[k].V_parent=this; // propagate parent reference to this field object
434                         vals[k].__ensure_instance_ids(visited);
435                       }
436                     }
437                     catch (Exception e) {
438                       log.error("Client error - could not access (fhval)array "+type.getName()+" in "+this.getClass().getName(), e);
439                     }
440                   //}
441                 } else {
442                   rf = (Vobject) fval;
443                   log.debug("Got value for "+fields[i].getFieldName());
444                 }
445               }
446             } else {
447               // castor's mechanism doesn't work for this object... so...*/
448               // fuck around, fuck around, jump up jump up and get down! */
449               Object o = fields[i].getClassDescriptor();
450               if (o!=null) {
451                 // XMLClassDescriptorImpl fclasdes = (XMLClassDescriptorImpl) o;
452                 String methname = "get"+fields[i].getFieldName();
453                 Method fgetmeth = this.getClass().getMethod(methname,(Class[])null);
454                 if (fgetmeth!=null) {
455                   Object fval = fgetmeth.invoke(this,(Object[])null);
456                   if (fval!=null)
457                     rf = (Vobject) fval;
458                 } else {
459                   log.warn("Couldn't find "+this.getClass().getName()+"."+methname);
460                 }
461               }
462             }
463             if (rf!=null) {
464               if (rf.__vorba==null)
465                 rf.__vorba = __vorba; // propagate IVorbaIdFactory
466               if (rf.V_parent==null)
467                 rf.V_parent=this; // propagate parent reference
468              rf.__ensure_instance_ids(visited);
469             }
470           }
471           catch (Exception e) {
472             log.error("Client error - could not access "+type.getName()+" in "+this.getClass().getName(), e);
473           }
474         }
475     }
476     
477   }
478
479   /**
480    * @return the __parent
481    */
482   public Vobject getV_parent() {
483     return V_parent;
484   }
485
486   /**
487    * @param __parent the __parent to set
488    */
489   protected void setV_parent(Vobject V_parent) {
490     this.V_parent = V_parent;
491   }
492   /**
493    * LhashValue - used for change detection between document updates.
494    */
495   private long __l_hash=0;
496   /**
497    * set the base LhashValue for this object
498    * @param checksum
499    */
500   protected void __setInitHash(long checksum) {
501     __l_hash = checksum;
502   }
503   /**
504    * compute the final LhashValue as a difference between checksum and the current base
505    * @param checksum
506    */
507   protected void __setFinalHash(long checksum) {
508     __l_hash = checksum - __l_hash;
509   }
510   /**
511    * get the LhashValue for this object
512    * @return the difference in values passed to __setFinalHash less __setInitHash
513    */
514   protected long __getLHash() {
515     return __l_hash;
516   }
517 }