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