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