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