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