/* * This file is part of the Vamsas Client version 0.2. * Copyright 2010 by Jim Procter, Iain Milne, Pierre Marguerite, * Andrew Waterhouse and Dominik Lindner. * * Earlier versions have also been incorporated into Jalview version 2.4 * since 2008, and TOPALi version 2 since 2007. * * The Vamsas Client is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The Vamsas Client is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the Vamsas Client. If not, see . */ 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); final VorbaIdFactory ourfactory = factory; unmarshaller.setIDResolver(new IDResolver() { public Object resolve(String id) { if (ourfactory.warnUnresolved) { // 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 factory.warnUnresolved = false; Object obj = unmarshaller.unmarshal(instream); factory.warnUnresolved = true; 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; } }