4 package uk.ac.vamsas.client;
6 import java.lang.reflect.Field;
7 import java.lang.reflect.InvocationTargetException;
8 import java.lang.reflect.Method;
9 import java.util.Iterator;
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;
17 import uk.ac.vamsas.test.simpleclient.VamsasArchive;
20 * Base class for all Vamsas objects extracted from an IClientDocument. An
21 * Vobject maybe registered or unregistered.
26 public abstract class Vobject {
27 static Log log = LogFactory.getLog(Vobject.class);
30 * true if Vobject was stored in a vamsas Document or has been retrieved from it
32 protected boolean __stored_in_document = false;
34 * true if Vobject was updated since the vamsas library last read a Vobj with the same VorbaId from a document.
36 protected boolean __updated_since_last_read = false;
38 * true if Vobject appeared in the document after the last access by this vamsas library instance
40 protected boolean __added_since_last_read = false;
42 * memory of the last doHash() value computed for the Vobject
45 protected int __last_hash = 0;
47 * set by testInstanceForIdField() if Vobject should have a VorbaId
49 protected boolean registerable = false;
50 protected boolean __visited = false;
52 * reference to containing object for this Vobject.
54 protected Vobject V_parent=null;
56 * unique id for all vamsas objects allows unambiguous referencing to any
57 * Vobject in the vamsas document
59 protected VorbaId vorbaId = null;
62 * the source of unique VorbaIds.
64 protected IVorbaIdFactory __vorba = null;
67 * @see java.lang.Object#finalize()
69 protected void finalize() throws Throwable {
81 testInstanceForIdField();
84 * Override Object.hashCode with base value for castor generated object hashcodes.
86 public int hashCode() {
89 java.lang.reflect.Field ___id_field=null; // set to ease pain of reflection
91 * set the isRegisterable flag based on the presence of a 'private String _id' field in
92 * the reflected class instance.
94 private void testInstanceForIdField() {
95 // TODO: decide if 'id' is an appropriate reserved attribute name for the VorbaId
96 // look for the id field in all castor classes (should be an NCName string)
98 Class thisclass=this.getClass();
99 setRegisterable(false);
100 while (!thisclass.equals(Vobject.class)) {
102 java.lang.reflect.Field fd = thisclass.getDeclaredField("_id");
103 if (String.class.isAssignableFrom(fd.getType())) {
105 this.setRegisterable(true);
108 } catch (SecurityException e) {
109 log.error("Unexpected Security Exception whilst finding id fields to set!",e);
110 } catch (NoSuchFieldException e) {
111 thisclass=thisclass.getSuperclass();
115 // boolean __testedInstance=false;
117 * update the Vobject instance's _id field, based on the contents of the
118 * VorbaId. Only call this if you mean to do it!
120 protected void setInstanceIdField() {
121 /*if (!registerable && !__testedInstance) {
122 testInstanceForIdField();
123 __testedInstance=true;
128 Method fd = this.getClass().getMethod("setId", new Class[] { String.class });
129 fd.invoke((Object) this, new Object[] {new String(this.getVorbaId().id)});
130 log.debug(this.getClass().getName()+" called setInstanceIdField!");
131 } catch (InvocationTargetException e) {
132 log.error("SourceGeneration of "
133 + this.getClass().toString()
134 + "\n has resulted in an inaccessible 'setId' method!\nCannot set ID from the vorbaId Vobject.", e);
136 catch (IllegalAccessException e) {
137 log.error("SourceGeneration of "
138 + this.getClass().toString()
139 + "\n has resulted in an inaccessible 'setId' method!\nCannot set ID from the vorbaId Vobject.", e);
140 } catch (SecurityException e) {
141 log.error("Security access violation for "+this.getClass().toString(),e);
142 } catch (NoSuchMethodException e) {
143 log.warn(this.getClass().toString()+" was erroneously marked as a Vorba Vobject class (Implementation error?)");
144 this.setRegisterable(false);
147 System.err.println("Client error. Trying to setInstanceIdField on a "
148 + this.getClass().toString() + " (which cannot be given a vorbaId)");
152 protected String __getInstanceIdField() {
153 /*if (!registerable && !__testedInstance) {
154 testInstanceForIdField();
155 __testedInstance=true;
160 Method fd = this.getClass().getMethod("getId", (Class[]) null);
161 Object idstring = fd.invoke((Object) this, (Object[]) null);
162 log.debug(this.getClass().getName()+" called getInstanceIdField!");
163 if (idstring!=null && idstring instanceof String) {
164 if (((String) idstring).length()>0)
165 return (String) idstring;
167 } catch (InvocationTargetException e) {
168 log.error("SourceGeneration of "
169 + this.getClass().toString()
170 + "\n has resulted in an inaccessible 'getId' method!\nCannot set ID from the vorbaId Vobject.", e);
172 catch (IllegalAccessException e) {
173 log.error("SourceGeneration of "
174 + this.getClass().toString()
175 + "\n has resulted in an inaccessible 'getId' method!\nCannot set ID from the vorbaId Vobject.", e);
176 } catch (SecurityException e) {
177 log.error("Security access violation for "+this.getClass().toString(),e);
178 } catch (NoSuchMethodException e) {
179 log.warn(this.getClass().toString()+" was erroneously marked as a Vorba Vobject class (Implementation error?)");
180 this.setRegisterable(false);
183 System.err.println("Client error. Trying to getInstanceIdField on a "
184 + this.getClass().toString() + " (which cannot be given a vorbaId)");
189 * calls the castor-generated hashCode() method
192 protected int __callHash() {
194 Method fd = this.getClass().getMethod("hashCode", (Class[]) null);
195 Object hashvalue = fd.invoke((Object) this, (Object[]) null);
196 if (log.isDebugEnabled())
197 log.debug(this.getClass().getName()+" called hashCode()!");
198 if (hashvalue!=null && hashvalue instanceof Integer) {
199 return ((Integer) hashvalue).intValue();
201 } catch (InvocationTargetException e) {
202 log.error("SourceGeneration of "
203 + this.getClass().toString()
204 + "\n has resulted in an inaccessible 'hashCode' method!\nHave you set org.exolab.castor.builder.equalsmethod=true in castorbuilder.properties ?.", e);
206 catch (IllegalAccessException e) {
207 log.error("SourceGeneration of "
208 + this.getClass().toString()
209 + "\n has resulted in an inaccessible 'hashCode' method!\nHave you set org.exolab.castor.builder.equalsmethod=true in castorbuilder.properties ?.", e);
210 } catch (SecurityException e) {
211 log.error("Security access violation for "+this.getClass().toString(),e);
212 } catch (NoSuchMethodException e) {
213 log.warn(this.getClass().toString()+" was erroneously extending from a Vorba Vobject class (Implementation error? no hashCode() method)" +
214 "\nHave you set org.exolab.castor.builder.equalsmethod=true in castorbuilder.properties ?.", e);
219 * calculate a hash for the Vobject with all housekeeping fields at standard
220 * values. (isRegisterable is an immutable attribute property)
221 * TODO: LATER: make this hash function compute a hash that truly reflects changes in Vobject attributes for benefit of update mechanism
222 * @return true if new hash different to last hash (or first time its been computed)
224 synchronized protected boolean doHash() {
225 boolean stored = __stored_in_document;
226 __stored_in_document=false;
227 boolean updated = __updated_since_last_read;
228 __updated_since_last_read=false;
229 boolean added_since=__added_since_last_read;
230 __added_since_last_read=false;
231 long __old_hash = __last_hash;
233 // leave registerable - doesn't change
234 boolean visited = __visited;
236 Vobject _V_parent=V_parent;
238 VorbaId thisid = vorbaId;
240 IVorbaIdFactory factory = __vorba;
242 java.lang.reflect.Field idfield = ___id_field;
244 long l_hash = __l_hash;
247 __last_hash = __callHash();
248 // reset houseskeeping variables
252 __stored_in_document = stored;
253 __updated_since_last_read=updated;
256 __added_since_last_read=added_since;
258 // return true if first time hash was computed or if hash has changed
259 return (__old_hash==0) || (__old_hash != __last_hash);
263 * TODO: combine two versions of the same collection Vobject to resolve
264 * asynchronous updates to the same vamsas Vobject Merges two vamsas objects,
265 * one of which is a later version of the earlier (ie they have the same
266 * vorbaId but one is a later version recently read from the vamsasDocument
271 protected boolean merge(Vobject laterCopy) {
272 log.warn(this.getClass().getName()+".merge() not implemented.");
278 * @return true if Vobject is registered
280 public boolean isRegistered() {
281 return (registerable) ? (vorbaId != null) : false;
285 * Method to get fixed reference for the Vobject in the vamsas document.
287 * @returns null if Vobject is neither registered or not associated with a
288 * properly instantiated VorbaIdFactory.
290 public VorbaId getVorbaId() {
291 if (registerable && vorbaId == null) {
292 if (this.__stored_in_document) {
294 vorbaId=uk.ac.vamsas.client.VorbaId.newId(this.__getInstanceIdField());
296 // Try to use the associated factory.
298 if ((vorbaId = __vorba.makeVorbaId(this)) == null)
299 return null; // Factory not valid.
301 this.setInstanceIdField();
309 * used by the IClient implementation to generate unique Id based on client
310 * applications current namespace.
312 protected void setVorbaId(VorbaId newid) {
317 * @return true if Vobject is present in Vamsas Document.
319 public boolean is__stored_in_document() {
320 return __stored_in_document;
324 * @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
326 public boolean isUpdated() {
327 return __updated_since_last_read;
331 * @return true if this object was added to the document after the last time the vamsas library acessed the session document
333 public boolean isNewInDocument() {
334 return __added_since_last_read;
337 * Set internal flag to indicate this object was updated since the last document read
338 * @param __updated_since_last_read the __updated_since_last_read to set
340 protected void set__updated_since_last_read(boolean __updated_since_last_read) {
341 this.__updated_since_last_read = __updated_since_last_read;
342 if(__updated_since_last_read && log.isDebugEnabled())
343 log.debug("Registered update for "+this.getVorbaId());
347 * for use by Vorba agent to reflect state of vamsas Vobject to client
349 * Setting stored_in_document on a registerable Vobject without a
350 * vorbaId will mean is will *never* get a vorbaId and
351 * horrible things will happen.
352 * @param __stored_in_document true if Vobject has been marshalled into current document.
354 protected void set__stored_in_document(boolean __stored_in_document) {
355 this.__stored_in_document = __stored_in_document;
356 if(__stored_in_document && log.isDebugEnabled())
357 log.debug("Retrieved document object: "+this.getVorbaId());
361 * @param __added_since_last_read the __added_since_last_read to set
363 protected void set__added_since_last_read(boolean __added_since_last_read) {
364 this.__added_since_last_read = __added_since_last_read;
366 if(__added_since_last_read && log.isDebugEnabled())
367 log.debug("New object in document: "+this.getVorbaId());
371 * __last_hash is the hash value computed when the Vobject was last checked
372 * against a IClientDocument generated by the Vobject's parent IClient
375 * @return Returns the __last_hash.
377 public long get__last_hash() {
382 * @return true if Vobject can have a vorbaId
384 public boolean isRegisterable() {
389 * Called by __testInstanceForidField and the post-unmarshalling handler
390 * to indicate if Vobject will have a vorbaId.
391 * @param registerable
393 protected void setRegisterable(boolean registerable) {
394 this.registerable = registerable;
397 * ensure's internal id field corresponds to vorbaId and
398 * cascade through all fields referring to an instance of Vobject
399 * calling the same method on them.
400 * TODO: LATER: properly apply castors own field mechanisms to get at accessors
401 * TODO: FIX CYCLIC __ensure+instance_ids
402 * Implementation note for the todo:
403 * this works like a depth-first search over all vamsas objects in an vamsasDocument.
404 * __visited is the visited flag, any Vobj who's flag is of a different parity
405 * to the visited argument will be recursed on.
406 * note - the doHash() function used to be used as the 'visited' flag -
407 * this *is not* a valid heuristic, although it will work "most of the time".
408 * TODO: LATER? Add another method for setDefaultProvenanceField (in the spirit of setInstanceIdField) using the info from the __vorba.getClient/User/Session methods
410 protected void __ensure_instance_ids() {
411 __ensure_instance_ids(!__visited);
413 protected void __ensure_instance_ids(boolean visited) {
415 throw new Error("Improperly intialised uk.ac.vamsas.client.Vobject - no VorbaFactory given.");
416 log.debug("doing "+this.getClass()+".__ensure_instance_ids()");
417 if (!__stored_in_document && registerable)
418 setInstanceIdField();
419 if (__visited==visited)
422 //__vorba.updateHashValue(this);
424 Class descriptor = null;
425 XMLClassDescriptorImpl descimpl = null;
427 // castor descriptor resolver magic
428 StringBuffer desname = new StringBuffer(this.getClass().getName());
429 desname.insert(desname.lastIndexOf("."), ".descriptors");
430 desname.append("Descriptor");
431 descriptor = this.getClass().getClassLoader().loadClass(desname.toString());
432 descimpl = (XMLClassDescriptorImpl) descriptor.getConstructor((Class[])null).newInstance((Object[])null);
433 } catch (Exception e) {
434 log.fatal("Source Generation Error!: Couldn't resolve descriptor for "
435 +this.getClass().getName()
436 +" was 'generate descriptors' set for castorbuilder.properties?");
439 FieldDescriptor fields[] = descimpl.getFields();
440 for (int i=0,j=fields.length; i<j; i++) {
441 Class type= fields[i].getFieldType();
442 if (type.isArray()) {
443 if (Vobject[].class.isAssignableFrom(type)) {
445 Object val = fields[i].getHandler().getValue(this);
447 Vobject vals[] = (Vobject[]) val;
448 for (int k=0, l=vals.length; k<l; k++) {
449 if (vals[k].__vorba==null)
450 vals[k].__vorba = __vorba; // propagate IVorbaIdFactory
451 if (vals[k].V_parent==null)
452 vals[k].V_parent=this; // propagate parent reference to this element.
453 vals[k].__ensure_instance_ids(visited);
457 catch (Exception e) {
458 log.error("Client error - could not access array "+type.getName()+" in "+this.getClass().getName(), e);
462 if (Vobject.class.isAssignableFrom(type)) {
464 FieldHandler fh = fields[i].getHandler();
467 Object fval = fh.getValue(this);
469 if (fval.getClass().isArray()) {
470 //if (Vobject[].class.isAssignableFrom(type)) {
472 Vobject vals[] = (Vobject[]) fval;
473 for (int k=0, l=vals.length; k<l; k++) {
474 if (vals[k].__vorba==null)
475 vals[k].__vorba = __vorba; // propagate IVorbaIdFactory
476 if (vals[k].V_parent==null)
477 vals[k].V_parent=this; // propagate parent reference to this field object
478 vals[k].__ensure_instance_ids(visited);
481 catch (Exception e) {
482 log.error("Client error - could not access (fhval)array "+type.getName()+" in "+this.getClass().getName(), e);
487 log.debug("Got value for "+fields[i].getFieldName());
491 // castor's mechanism doesn't work for this object... so...*/
492 // fuck around, fuck around, jump up jump up and get down! */
493 Object o = fields[i].getClassDescriptor();
495 // XMLClassDescriptorImpl fclasdes = (XMLClassDescriptorImpl) o;
496 String methname = "get"+fields[i].getFieldName();
497 Method fgetmeth = this.getClass().getMethod(methname,(Class[])null);
498 if (fgetmeth!=null) {
499 Object fval = fgetmeth.invoke(this,(Object[])null);
503 log.warn("Couldn't find "+this.getClass().getName()+"."+methname);
508 if (rf.__vorba==null)
509 rf.__vorba = __vorba; // propagate IVorbaIdFactory
510 if (rf.V_parent==null)
511 rf.V_parent=this; // propagate parent reference
512 rf.__ensure_instance_ids(visited);
515 catch (Exception e) {
516 log.error("Client error - could not access "+type.getName()+" in "+this.getClass().getName(), e);
524 * @return the __parent
526 public Vobject getV_parent() {
531 * @param __parent the __parent to set
533 protected void setV_parent(Vobject V_parent) {
534 this.V_parent = V_parent;
537 * LhashValue - used for change detection between document updates.
539 private long __l_hash=0;
541 * set the base LhashValue for this object
544 protected void __setInitHash(long checksum) {
548 * compute the final LhashValue as a difference between checksum and the current base
551 protected void __setFinalHash(long checksum) {
552 __l_hash = checksum - __l_hash;
555 * get the LhashValue for this object
556 * @return the difference in values passed to __setFinalHash less __setInitHash
558 protected long __getLHash() {