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