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