4 package uk.ac.vamsas.client;
6 import java.io.IOException;
7 import java.io.PrintWriter;
10 import java.lang.reflect.Field;
11 import java.util.Hashtable;
12 import java.util.Iterator;
13 import java.util.Vector;
15 import org.apache.commons.logging.Log;
16 import org.apache.commons.logging.LogFactory;
17 import org.exolab.castor.mapping.FieldHandler;
18 import org.exolab.castor.mapping.GeneralizedFieldHandler;
19 import org.exolab.castor.mapping.ValidityException;
20 import org.exolab.castor.xml.IDResolver;
21 import org.exolab.castor.xml.MarshalException;
22 import org.exolab.castor.xml.MarshalListener;
23 import org.exolab.castor.xml.Marshaller;
24 import org.exolab.castor.xml.UnmarshalListener;
25 import org.exolab.castor.xml.Unmarshaller;
26 import org.exolab.castor.xml.ValidationException;
28 import uk.ac.vamsas.client.utils.ChecksummedReader;
29 import uk.ac.vamsas.objects.core.VamsasDocument;
31 * Implements the Vamsas Vobject ID machinery for translating
32 * between non-volatile XML IDs and Vobject references. Use the
33 * marshalling and unmarshalling methods in this class in order
34 * to add automatically computed values for required fields in objects,
35 * so as to avoid validation exceptions when marshalling new objects
36 * into the vamsas document.
38 public class VorbaXmlBinder implements UnmarshalListener {
39 private static Log log = LogFactory.getLog(VorbaXmlBinder.class);
40 private final IVorbaIdFactory vorbafactory;
42 private final Vector obj;
43 private final Hashtable oldobjhashes;
44 private final Hashtable objrefs;
45 private final Vector updatedobjs; // not yet used elswhere ?
46 private final ChecksummedReader inStream;
47 public VorbaXmlBinder(IVorbaIdFactory vorbafactory, Vector obj, Hashtable objrefs, Hashtable oldobjhashes, Vector updatedobjs) {
48 this(vorbafactory, obj, objrefs, oldobjhashes, updatedobjs, null);
52 public VorbaXmlBinder(IVorbaIdFactory vorbafactory2, Vector unrefedObj, Hashtable objrefs2, Hashtable oldobjhashes, Vector updatedObj, ChecksummedReader ckedInstream) {
53 this.inStream = ckedInstream;
54 this.vorbafactory = vorbafactory2;
55 this.obj = unrefedObj;
56 this.objrefs = objrefs2;
57 this.oldobjhashes = oldobjhashes;
58 this.updatedobjs = updatedObj;
64 * @see org.exolab.castor.xml.UnmarshalListener#attributesProcessed(java.lang.Object)
66 public void attributesProcessed(Object object) {
73 * @see org.exolab.castor.xml.UnmarshalListener#fieldAdded(java.lang.String,
74 * java.lang.Object, java.lang.Object)
76 public void fieldAdded(String fieldName, Object parent, Object child) {
77 if (parent instanceof Vobject && child instanceof Vobject) {
78 if (((Vobject) child).V_parent==null) {
79 // System.err.println("Setting parent of "+fieldName);
80 ((Vobject) child).setV_parent((Vobject) parent);
88 * @see org.exolab.castor.xml.UnmarshalListener#initialized(java.lang.Object)
90 public void initialized(Object object) {
91 if (object instanceof Vobject) {
92 Vobject nobj = (Vobject) object;
93 if (nobj.isRegisterable() && nobj.___id_field!=null && inStream!=null)
94 nobj.__setInitHash(inStream.getChecksum()); // record start tag position
99 * Check if the object has an 'id' field - if it does, copy the value into
100 * the VorbaId field of Vobject, and add the Vobject to the VorbaId hash.
102 * @see org.exolab.castor.xml.UnmarshalListener#unmarshalled(java.lang.Object)
104 public void unmarshalled(Object newobj) {
105 if (newobj instanceof Vobject) {
106 Vobject nobj = (Vobject) newobj;
107 nobj.set__stored_in_document(true);
109 if (nobj.isRegisterable() && nobj.___id_field!=null) {
111 nobj.__setFinalHash(inStream.getChecksum()); // compute LHash as difference between end and start tag positions
112 VorbaId nobj_id=null;
113 // look for the id field (should be an NCName string)
114 nobj.__vorba = vorbafactory;
115 // use the Vobject accessor method to avoid unpleasant security exceptions.
116 String idstring = nobj.__getInstanceIdField();
117 if (idstring!=null) {
118 if (idstring.length() > 0) {
119 nobj.setVorbaId(VorbaId.newId(idstring));
120 nobj_id=nobj.getVorbaId();
121 if (objrefs.containsKey(nobj_id) && !objrefs.get(nobj_id).equals(nobj)) {
122 System.err.println("Serious problem : duplicate id '"+idstring+"' found! expect badness.");
123 // TODO: HANDLE duplicate XML ids correctly
125 objrefs.put(nobj_id, nobj);
127 // add to list of objects without a valid vorbaId
131 // TODO: add to list of objects without a valid vorbaId
134 nobj.doHash(); // TODO: DECIDE IF WE NEED TO DO THIS STILL: THIS DOESNT WORK! updates detected by comparing with last hash when marshalling
136 // check to see if new object was present in old object hash
138 if (oldobjhashes.containsKey(nobj_id)) {
139 Vobjhash oldhash = (Vobjhash) oldobjhashes.get(nobj_id);
140 if (oldhash.isUpdated(nobj)) {
141 // mark the object as updated in this document read.
142 nobj.set__updated_since_last_read(true);
143 oldobjhashes.put(nobj_id, new Vobjhash(nobj));
144 updatedobjs.addElement(nobj);
147 // object has no entry in the oldhashvalue list but
148 // there is a valid vorbaId so
149 nobj.set__added_since_last_read(true);
151 // and record the just-unmarshalled hash value
152 oldobjhashes.put(nobj_id, new Vobjhash(nobj));
154 // for objects yet to get a valid vorba_id
155 nobj.set__added_since_last_read(true);
159 } catch (Exception e) {
167 * writes the VamsasDocument to the given stream.
168 * TODO: ensure that (at least) default provenance entries are written for objects.
170 * @param vorba valid VorbaIdFactory to construct any missing IDs
172 * @throws IOException
173 * @throws MarshalException
174 * @throws ValidationException
176 public static void putVamsasDocument(PrintWriter outstream, VorbaIdFactory vorba, VamsasDocument doc)
177 throws IOException, MarshalException, ValidationException {
180 throw new Error("Null VorbaIdFactory Parameter");
181 if (doc.__vorba==null)
183 doc.__ensure_instance_ids(); // this may take a while. Do we allow for cyclic references ?
184 doc.marshal(outstream);
188 * creates new VorbaId references where necessary for newly unmarshalled objects
191 * @return false if any new object references were made
193 private static boolean ensure_references(Vector unrefed, Hashtable objrefs) {
195 if (unrefed.size()>0) {
196 sync=false; // document is out of sync - ids have been created.
197 java.util.Iterator newobj = unrefed.listIterator();
198 while (newobj.hasNext()) {
199 Vobject o = (Vobject) newobj.next();
200 // forces registration and id field update.
201 VorbaId id = o.getVorbaId();
202 if (!objrefs.containsKey(id)) {
205 if (!objrefs.get(id).equals(o))
206 throw new Error("Serious! Duplicate reference made by vorbaIdFactory!");
213 * Unmarshals a vamsasDocument Vobject from a stream, registers
214 * unregistered objects, records existing VorbaIds, and completes
215 * the uk.ac.vamsas.client.Vobject housekeeping fields.
216 * For a valid unmarshalling, the array of returned objects also includes
217 * a <return>sync</return> parameter which is true if new VorbaIds
218 * were created. If sync is false, then the caller should ensure that the
219 * vamsasDocument is written back to disk to propagate the new VorbaIds.
220 * TODO: ensure that provenance is correct for newly registered objects
221 * as getVamsasObjects but will detect updated objects based on differing hash values
222 * obtained from the VorbaIdFactory's VorbaId, Vobject.get__last_Hash() pairs (if any)
223 * @param instream - the XML input stream
224 * @param factory - the SimpleClient's properly configured VorbaId factory to make new references.
225 * @param root the root element's uk.ac.vamsas.objects.core Vobject.
226 * @return null or {(Object) VamsasDocument Vobject, (Object) Hashtable of Vobject references, (Object) Boolean(sync), (Object) Vector of updated objects in document }
228 public static Object[] getVamsasObjects(Reader instream,
229 VorbaIdFactory factory, Vobject root) {
230 Unmarshaller unmarshaller = new Unmarshaller(root);
231 unmarshaller.setIDResolver(new IDResolver() {
232 public Object resolve(String id) {
233 VorbaXmlBinder.log.warn("Warning - id " + id
234 + " is not found in the Vamsas XML!");
238 final Hashtable objrefs = new Hashtable();
239 if (factory.extanthashv==null)
240 factory.extanthashv=new Hashtable();
241 final Hashtable oobjhashes=factory.extanthashv;
242 final VorbaIdFactory vorbafactory = factory;
243 final Vector unrefedObj = new Vector();
244 final Vector updatedObj = new Vector();
245 ChecksummedReader ckedInstream = new ChecksummedReader(instream);
246 unmarshaller.setUnmarshalListener(new VorbaXmlBinder(vorbafactory, unrefedObj, objrefs, oobjhashes,updatedObj, ckedInstream));
247 // Call the unmarshaller.
249 while (instream.ready()) {
250 // TODO: mark objects in oobjhash prior to unmarshalling, to detect when objects have been lost through an update.
252 Object obj = unmarshaller.unmarshal(ckedInstream);
253 boolean sync=ensure_references(unrefedObj, objrefs);
254 if (!(obj instanceof Vobject))
256 vorbafactory.setNewIdHash(objrefs); // update the Document IO Handler's set of vorbaId<>Object bindings.
257 return new Object[] { obj, objrefs, new Boolean(sync),updatedObj};
259 } catch (MarshalException e) {
260 // TODO Auto-generated catch block
262 } catch (ValidationException e) {
263 // TODO Auto-generated catch block
265 } catch (IOException e) {
266 // TODO Auto-generated catch block