bugfixed org.vamsas.test.simpleclient.ArchiveClient and made VorbaId mechanism more...
[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
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           this.setRegisterable(true);
90           break;
91         }
92       } catch (SecurityException e) {
93         log.error("Unexpected Security Exception whilst finding id fields to set!",e);
94       } catch (NoSuchFieldException e) {
95         thisclass=thisclass.getSuperclass();
96       }
97     }
98   }
99   // boolean __testedInstance=false;
100   /**
101    * update the Vobject instance's _id field, based on the contents of the
102    * VorbaId. Only call this if you mean to do it!
103    */
104   protected void setInstanceIdField() {
105     /*if (!registerable && !__testedInstance) {
106       testInstanceForIdField();
107       __testedInstance=true;
108     }*/
109     if (registerable) {
110       if (__vorba != null)
111         try {
112           Method fd = this.getClass().getMethod("setId", new Class[] { String.class });
113           fd.invoke((Object) this, new Object[] {new String(this.getVorbaId().id)});
114           log.debug(this.getClass().getName()+" called setInstanceIdField!");
115         } catch (InvocationTargetException e) { 
116           log.error("SourceGeneration of "
117               + this.getClass().toString()
118               + "\n has resulted in an inaccessible 'setId' method!\nCannot set ID from the vorbaId Vobject.", e);
119         }
120         catch (IllegalAccessException 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         } catch (SecurityException e) {
125           log.error("Security access violation for "+this.getClass().toString(),e);
126         } catch (NoSuchMethodException e) {
127           log.warn(this.getClass().toString()+" was erroneously marked as a Vorba Vobject class (Implementation error?)");
128           this.setRegisterable(false);
129         }
130     } else {
131       System.err.println("Client error. Trying to setInstanceIdField on a "
132           + this.getClass().toString() + " (which cannot be given a vorbaId)");
133     }
134   }
135
136   /**
137    * calculate a hash for the Vobject with all housekeeping fields at standard
138    * values. (isRegisterable is an immutable attribute property)
139    * TODO: decide if __stored_in_document should be included in the hash or not.
140    * @return true if new hash different to last hash
141    */
142   synchronized protected boolean doHash() {
143     long __old_hash = __last_hash;
144     __last_hash = 0;
145     Vobject _V_parent=V_parent;
146     V_parent=null;
147     VorbaId thisid = vorbaId;
148     IVorbaIdFactory factory = __vorba;
149     boolean stored = __stored_in_document;
150     vorbaId = null;
151     __vorba = null;
152     __last_hash = this.hashCode();
153     vorbaId = thisid;
154     __vorba = factory;
155     __stored_in_document = stored;
156     V_parent=_V_parent;
157     return (__old_hash==0) || (__old_hash == __last_hash);
158   }
159
160   /**
161    * TODO: combine two versions of the same collection Vobject to resolve
162    * asynchronous updates to the same vamsas Vobject Merges two vamsas objects,
163    * one of which is a later version of the earlier (ie they have the same
164    * vorbaId but one is a later version recently read from the vamsasDocument
165    * collection.
166    * 
167    * @return
168    */
169   protected boolean merge(Vobject laterCopy) {
170     log.warn(this.getClass().getName()+".merge() not implemented.");
171     return true;
172   }
173
174   /**
175    * 
176    * @return true if Vobject is registered
177    */
178   public boolean isRegistered() {
179     return (registerable) ? (vorbaId != null) : false;
180   }
181
182   /**
183    * Method to get fixed reference for the Vobject in the vamsas document.
184    * 
185    * @returns null if Vobject is neither registered or not associated with a
186    *          properly instantiated VorbaIdFactory.
187    */
188   public VorbaId getVorbaId() {
189     /*if (!registerable && !__testedInstance) {
190       testInstanceForIdField();
191       __testedInstance=true;
192     }*/
193     if (registerable && vorbaId == null) {
194       // Try to use the associated factory.
195       if (__vorba != null)
196         if ((vorbaId = __vorba.makeVorbaId(this)) == null)
197           return null; // Factory not valid.
198         else {
199           this.setInstanceIdField();
200           return vorbaId;
201         }
202     }
203     return vorbaId;
204   }
205
206   /**
207    * used by the IClient implementation to generate unique Id based on client
208    * applications current namespace.
209    */
210   protected void setVorbaId(VorbaId newid) {
211     vorbaId = newid;
212   }
213
214   /**
215    * @return true if Vobject is present in Vamsas Document.
216    */
217   public boolean is__stored_in_document() {
218     return __stored_in_document;
219   }
220
221   /**
222    * for use by Vorba agent to reflect state of vamsas Vobject to client
223    * application.
224    * Setting stored_in_document on a registerable Vobject without a 
225    * vorbaId will mean is will *never* get a vorbaId and 
226    * horrible things will happen.
227    * @param __stored_in_document true if Vobject has been marshalled into current document.
228    */
229   protected void set__stored_in_document(boolean __stored_in_document) {
230     this.__stored_in_document = __stored_in_document;
231   }
232
233   /**
234    * __last_hash is the hash value computed when the Vobject was last checked
235    * against a IClientDocument generated by the Vobject's parent IClient
236    * instance.
237    * 
238    * @return Returns the __last_hash.
239    */
240   public long get__last_hash() {
241     return __last_hash;
242   }
243
244   /**
245    * @return true if Vobject can have a vorbaId
246    */
247   public boolean isRegisterable() {
248     return registerable;
249   }
250
251   /**
252    * Called by __testInstanceForidField and the post-unmarshalling handler
253    * to indicate if Vobject will have a vorbaId.
254    * @param registerable 
255    */
256   protected void setRegisterable(boolean registerable) {
257     this.registerable = registerable;
258   }
259   /**
260    * ensure's internal id field corresponds to vorbaId and
261    * cascade through all fields referring to an instance of Vobject
262    * calling the same method on them.
263    * TODO: LATER: properly apply castors own field mechanisms to get at accessors
264    * TODO: FIX CYCLIC __ensure+instance_ids
265    * Implementation note for the todo:
266    * this works like a depth-first search over all vamsas objects in an vamsasDocument. 
267    * The doHash() function is used as the 'visited' flag - 
268    * this *is not* a valid heuristic, although it will work "most of the time".
269    * TODO: LATER? Add another method for setDefaultProvenanceField (in the spirit of setInstanceIdField) using the info from the __vorba.getClient/User/Session methods 
270    */
271   protected void __ensure_instance_ids() {
272     if (__vorba==null)
273       throw new Error("Improperly intialised org.vamsas.client.Vobject - no VorbaFactory given.");
274     log.debug("doing "+this.getClass()+".__ensure_instance_ids()");
275     if (!__stored_in_document && registerable)
276       setInstanceIdField();
277     if (!doHash())
278       return; // nothing has changed in this Vobject - probably visited it before.
279     Class descriptor = null;
280     XMLClassDescriptorImpl descimpl = null;
281     try {
282       // castor descriptor resolver magic
283       descriptor = this.getClass().getClassLoader().loadClass(this.getClass().getName()+"Descriptor");
284       descimpl = (XMLClassDescriptorImpl) descriptor.getConstructor((Class[])null).newInstance((Object[])null);
285     } catch (Exception e) {
286       log.fatal("Source Generation Error!: Couldn't resolve descriptor for "
287           +this.getClass().getName()
288           +" was 'generate descriptors' set for castorbuilder.properties?");
289       return;
290     }
291     FieldDescriptor fields[] = descimpl.getFields();
292     for (int i=0,j=fields.length; i<j; i++) {
293       Class type= fields[i].getFieldType();
294       if (type.isArray()) {
295         if (Vobject[].class.isAssignableFrom(type)) {
296           try {
297             Object val = fields[i].getHandler().getValue(this);
298             if (val!=null) {
299               Vobject vals[] = (Vobject[]) val; 
300               for (int k=0, l=vals.length; k<l; k++) {
301                 if (vals[k].__vorba==null)
302                   vals[k].__vorba = __vorba; // propagate IVorbaIdFactory
303                 if (vals[k].V_parent==null)
304                   vals[k].V_parent=this; // propagate parent reference to this element.
305                 vals[k].__ensure_instance_ids();
306               }
307             }
308           }
309           catch (Exception e) {
310             log.error("Client error - could not access array "+type.getName()+" in "+this.getClass().getName(), e);
311           }
312         }
313       } else
314         if (Vobject.class.isAssignableFrom(type)) {
315           try {
316             FieldHandler fh = fields[i].getHandler();
317             Vobject rf = null;
318             if (fh != null) {
319               Object fval = fh.getValue(this);
320               if (fval!=null) {
321                 if (fval.getClass().isArray()) {
322                   //if (Vobject[].class.isAssignableFrom(type)) {
323                     try {
324                       Vobject vals[] = (Vobject[]) fval; 
325                       for (int k=0, l=vals.length; k<l; k++) {
326                         if (vals[k].__vorba==null)
327                           vals[k].__vorba = __vorba; // propagate IVorbaIdFactory
328                         if (vals[k].V_parent==null)
329                           vals[k].V_parent=this; // propagate parent reference to this field object
330                         vals[k].__ensure_instance_ids();
331                       }
332                     }
333                     catch (Exception e) {
334                       log.error("Client error - could not access (fhval)array "+type.getName()+" in "+this.getClass().getName(), e);
335                     }
336                   //}
337                 } else {
338                   rf = (Vobject) fval;
339                   log.debug("Got value for "+fields[i].getFieldName());
340                 }
341               }
342             } else {
343               // castor's mechanism doesn't work for this object... so...*/
344               // fuck around, fuck around, jump up jump up and get down! */
345               Object o = fields[i].getClassDescriptor();
346               if (o!=null) {
347                 // XMLClassDescriptorImpl fclasdes = (XMLClassDescriptorImpl) o;
348                 String methname = "get"+fields[i].getFieldName();
349                 Method fgetmeth = this.getClass().getMethod(methname,(Class[])null);
350                 if (fgetmeth!=null) {
351                   Object fval = fgetmeth.invoke(this,(Object[])null);
352                   if (fval!=null)
353                     rf = (Vobject) fval;
354                 } else {
355                   log.warn("Couldn't find "+this.getClass().getName()+"."+methname);
356                 }
357               }
358             }
359             if (rf!=null) {
360               if (rf.__vorba==null)
361                 rf.__vorba = __vorba; // propagate IVorbaIdFactory
362               if (rf.V_parent==null)
363                 rf.V_parent=this; // propagate parent reference
364              rf.__ensure_instance_ids();
365             }
366           }
367           catch (Exception e) {
368             log.error("Client error - could not access "+type.getName()+" in "+this.getClass().getName(), e);
369           }
370         }
371     }
372     
373   }
374
375   /**
376    * @return the __parent
377    */
378   public Vobject getV_parent() {
379     return V_parent;
380   }
381
382   /**
383    * @param __parent the __parent to set
384    */
385   protected void setV_parent(Vobject V_parent) {
386     this.V_parent = V_parent;
387   }
388 }