/** * */ package uk.ac.vamsas.client; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.io.Writer; import java.lang.reflect.Field; import java.util.Hashtable; import java.util.Iterator; import java.util.Vector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.exolab.castor.mapping.FieldHandler; import org.exolab.castor.mapping.GeneralizedFieldHandler; import org.exolab.castor.mapping.ValidityException; import org.exolab.castor.xml.IDResolver; import org.exolab.castor.xml.MarshalException; import org.exolab.castor.xml.MarshalListener; import org.exolab.castor.xml.Marshaller; import org.exolab.castor.xml.UnmarshalListener; import org.exolab.castor.xml.Unmarshaller; import org.exolab.castor.xml.ValidationException; import uk.ac.vamsas.objects.core.VamsasDocument; /** * Implements the Vamsas Vobject ID machinery for translating * between non-volatile XML IDs and Vobject references. Use the * marshalling and unmarshalling methods in this class in order * to add automatically computed values for required fields in objects, * so as to avoid validation exceptions when marshalling new objects * into the vamsas document. */ public class VorbaXmlBinder implements UnmarshalListener { private static Log log = LogFactory.getLog(VorbaXmlBinder.class); private final IVorbaIdFactory vorbafactory; private final Vector obj; private final Hashtable oldobjhashes; private final Hashtable objrefs; private final Vector updatedobjs; // not yet used elswhere ? public VorbaXmlBinder(IVorbaIdFactory vorbafactory2, Vector unrefedObj, Hashtable objrefs2, Hashtable oldobjhashes, Vector updatedObj) { this.vorbafactory = vorbafactory2; this.obj = unrefedObj; this.objrefs = objrefs2; this.oldobjhashes = oldobjhashes; this.updatedobjs = updatedObj; } /* * (non-Javadoc) * * @see org.exolab.castor.xml.UnmarshalListener#attributesProcessed(java.lang.Object) */ public void attributesProcessed(Object object) { } /* * (non-Javadoc) * * @see org.exolab.castor.xml.UnmarshalListener#fieldAdded(java.lang.String, * java.lang.Object, java.lang.Object) */ public void fieldAdded(String fieldName, Object parent, Object child) { if (parent instanceof Vobject && child instanceof Vobject) { if (((Vobject) child).V_parent==null) { // System.err.println("Setting parent of "+fieldName); ((Vobject) child).setV_parent((Vobject) parent); } } } /* * (non-Javadoc) * * @see org.exolab.castor.xml.UnmarshalListener#initialized(java.lang.Object) */ public void initialized(Object object) { if (object instanceof Vobject) { Vobject nobj = (Vobject) object; } } /* * Check if the object has an 'id' field - if it does, copy the value into * the VorbaId field of Vobject, and add the Vobject to the VorbaId hash. * * @see org.exolab.castor.xml.UnmarshalListener#unmarshalled(java.lang.Object) */ public void unmarshalled(Object newobj) { if (newobj instanceof Vobject) { Vobject nobj = (Vobject) newobj; nobj.set__stored_in_document(true); try { if (nobj.isRegisterable() && nobj.___id_field!=null) { VorbaId nobj_id=null; // look for the id field (should be an NCName string) nobj.__vorba = vorbafactory; // use the Vobject accessor method to avoid unpleasant security exceptions. String idstring = nobj.__getInstanceIdField(); if (idstring!=null) { if (idstring.length() > 0) { nobj.setVorbaId(VorbaId.newId(idstring)); nobj_id=nobj.getVorbaId(); if (objrefs.containsKey(nobj_id) && !objrefs.get(nobj_id).equals(nobj)) { System.err.println("Serious problem : duplicate id '"+idstring+"' found! expect badness."); // TODO: HANDLE duplicate XML ids correctly } objrefs.put(nobj_id, nobj); } else { // add to list of objects without a valid vorbaId obj.add(nobj); } } else { // TODO: add to list of objects without a valid vorbaId obj.add(nobj); } nobj.doHash(); // updates detected by comparing with last hash when marshalling // check to see if new object was present in old object hash if (nobj_id!=null) { if (oldobjhashes.containsKey(nobj_id)) { Vobjhash oldhash = (Vobjhash) oldobjhashes.get(nobj_id); if (oldhash.isUpdated(nobj)) { // mark the object as updated in this document read. nobj.set__updated_since_last_read(true); updatedobjs.addElement(nobj); } } else { // object has no entry in the oldhashvalue list but // there is a valid vorbaId so nobj.set__added_since_last_read(true); } // and record the just-unmarshalled hash value oldobjhashes.put(nobj_id, new Vobjhash(nobj)); } else { // for objects yet to get a valid vorba_id nobj.set__added_since_last_read(true); } } } catch (Exception e) { return; }; } } /** * writes the VamsasDocument to the given stream. * TODO: ensure that (at least) default provenance entries are written for objects. * @param outstream * @param vorba valid VorbaIdFactory to construct any missing IDs * @param doc * @throws IOException * @throws MarshalException * @throws ValidationException */ public static void putVamsasDocument(PrintWriter outstream, VorbaIdFactory vorba, VamsasDocument doc) throws IOException, MarshalException, ValidationException { // Ensure references if (vorba==null) throw new Error("Null VorbaIdFactory Parameter"); if (doc.__vorba==null) doc.__vorba = vorba; doc.__ensure_instance_ids(); // this may take a while. Do we allow for cyclic references ? Marshaller mshl = new Marshaller(outstream); mshl.marshal(doc); } /** * creates new VorbaId references where necessary for newly unmarshalled objects * @param unrefed * @param objrefs * @return false if any new object references were made */ private static boolean ensure_references(Vector unrefed, Hashtable objrefs) { boolean sync=true; if (unrefed.size()>0) { sync=false; // document is out of sync - ids have been created. java.util.Iterator newobj = unrefed.listIterator(); while (newobj.hasNext()) { Vobject o = (Vobject) newobj.next(); // forces registration and id field update. VorbaId id = o.getVorbaId(); if (!objrefs.containsKey(id)) { objrefs.put(id, o); } else { if (!objrefs.get(id).equals(o)) throw new Error("Serious! Duplicate reference made by vorbaIdFactory!"); } } } return sync; } /** * Unmarshals a vamsasDocument Vobject from a stream, registers * unregistered objects, records existing VorbaIds, and completes * the uk.ac.vamsas.client.Vobject housekeeping fields. * For a valid unmarshalling, the array of returned objects also includes * a sync parameter which is true if new VorbaIds * were created. If sync is false, then the caller should ensure that the * vamsasDocument is written back to disk to propagate the new VorbaIds. * TODO: ensure that provenance is correct for newly registered objects * as getVamsasObjects but will detect updated objects based on differing hash values * obtained from the VorbaIdFactory's VorbaId, Vobject.get__last_Hash() pairs (if any) * @param instream - the XML input stream * @param factory - the SimpleClient's properly configured VorbaId factory to make new references. * @param root the root element's uk.ac.vamsas.objects.core Vobject. * @return null or {(Object) VamsasDocument Vobject, (Object) Hashtable of Vobject references, (Object) Boolean(sync), (Object) Vector of updated objects in document } */ public static Object[] getVamsasObjects(Reader instream, VorbaIdFactory factory, Vobject root) { Unmarshaller unmarshaller = new Unmarshaller(root); unmarshaller.setIDResolver(new IDResolver() { public Object resolve(String id) { // TODO: allow for external ID resolution VorbaXmlBinder.log.warn("Warning - id " + id + " is not found in the Vamsas XML! (TODO: Ignore if this is a forward reference!)"); return null; } }); final Hashtable objrefs = new Hashtable(); if (factory.extanthashv==null) factory.extanthashv=new Hashtable(); final Hashtable oobjhashes=factory.extanthashv; final VorbaIdFactory vorbafactory = factory; final Vector unrefedObj = new Vector(); final Vector updatedObj = new Vector(); unmarshaller.setUnmarshalListener(new VorbaXmlBinder(vorbafactory, unrefedObj, objrefs, oobjhashes,updatedObj)); // Call the unmarshaller. try { while (instream.ready()) { // TODO: mark objects in oobjhash prior to unmarshalling, to detect when objects have been lost through an update. //tohere Object obj = unmarshaller.unmarshal(instream); boolean sync=ensure_references(unrefedObj, objrefs); if (!(obj instanceof Vobject)) return null; vorbafactory.setNewIdHash(objrefs); // update the Document IO Handler's set of vorbaId<>Object bindings. return new Object[] { obj, objrefs, new Boolean(sync),updatedObj}; } } catch (MarshalException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ValidationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } }