/*
* This file is part of the Vamsas Client version 0.1.
* Copyright 2009 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;
}
}