update detection mechanism based comparing the difference between the position of...
[vamsas.git] / src / uk / ac / vamsas / client / VorbaXmlBinder.java
1 /**
2  * 
3  */
4 package uk.ac.vamsas.client;
5
6 import java.io.IOException;
7 import java.io.PrintWriter;
8 import java.io.Reader;
9 import java.io.Writer;
10 import java.lang.reflect.Field;
11 import java.util.Hashtable;
12 import java.util.Iterator;
13 import java.util.Vector;
14
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;
27
28 import uk.ac.vamsas.client.utils.ChecksummedReader;
29 import uk.ac.vamsas.objects.core.VamsasDocument;
30 /**
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.
37  */
38 public class VorbaXmlBinder implements UnmarshalListener {
39   private static Log log = LogFactory.getLog(VorbaXmlBinder.class);
40   private final IVorbaIdFactory vorbafactory;
41
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);
49
50   }
51
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;
59   }
60
61   /*
62    * (non-Javadoc)
63    * 
64    * @see org.exolab.castor.xml.UnmarshalListener#attributesProcessed(java.lang.Object)
65    */
66   public void attributesProcessed(Object object) {
67
68   }
69
70   /*
71    * (non-Javadoc)
72    * 
73    * @see org.exolab.castor.xml.UnmarshalListener#fieldAdded(java.lang.String,
74    *      java.lang.Object, java.lang.Object)
75    */
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);
81       }
82     }
83   }
84
85   /*
86    * (non-Javadoc)
87    * 
88    * @see org.exolab.castor.xml.UnmarshalListener#initialized(java.lang.Object)
89    */
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
95     }
96   }
97
98   /*
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.
101    * 
102    * @see org.exolab.castor.xml.UnmarshalListener#unmarshalled(java.lang.Object)
103    */
104   public void unmarshalled(Object newobj) {
105     if (newobj instanceof Vobject) {
106       Vobject nobj = (Vobject) newobj;
107       nobj.set__stored_in_document(true);
108       try {
109         if (nobj.isRegisterable() && nobj.___id_field!=null) {
110           if (inStream!=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
124               }
125               objrefs.put(nobj_id, nobj);
126             } else {
127               // add to list of objects without a valid vorbaId
128               obj.add(nobj);
129             }
130           } else {
131             // TODO: add to list of objects without a valid vorbaId
132             obj.add(nobj);
133           }
134           nobj.doHash(); // TODO: DECIDE IF WE NEED TO DO THIS STILL: THIS DOESNT WORK! updates detected by comparing with last hash when marshalling
135
136           // check to see if new object was present in old object hash
137           if (nobj_id!=null) {
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);
145               }
146             } else {
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);
150             }
151             // and record the just-unmarshalled hash value
152             oldobjhashes.put(nobj_id, new Vobjhash(nobj));
153           } else {
154             // for objects yet to get a valid vorba_id
155             nobj.set__added_since_last_read(true);
156           }
157         }
158
159       } catch (Exception e) {
160         return;
161       };
162
163     }
164   }
165
166   /**
167    * writes the VamsasDocument to the given stream.
168    * TODO: ensure that (at least) default provenance entries are written for objects.
169    * @param outstream
170    * @param vorba valid VorbaIdFactory to construct any missing IDs 
171    * @param doc
172    * @throws IOException
173    * @throws MarshalException
174    * @throws ValidationException
175    */
176   public static void putVamsasDocument(PrintWriter outstream, VorbaIdFactory vorba, VamsasDocument doc)
177   throws IOException, MarshalException, ValidationException {
178     // Ensure references
179     if (vorba==null)
180       throw new Error("Null VorbaIdFactory Parameter");
181     if (doc.__vorba==null)
182       doc.__vorba = vorba;
183     doc.__ensure_instance_ids(); // this may take a while. Do we allow for cyclic references ? 
184     doc.marshal(outstream);
185
186   }
187   /**
188    * creates new VorbaId references where necessary for newly unmarshalled objects
189    * @param unrefed
190    * @param objrefs
191    * @return false if any new object references were made
192    */
193   private static boolean ensure_references(Vector unrefed, Hashtable objrefs) {
194     boolean sync=true;
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)) {
203           objrefs.put(id, o);
204         } else {
205           if (!objrefs.get(id).equals(o))
206             throw new Error("Serious! Duplicate reference made by vorbaIdFactory!");
207         }
208       }
209     }
210     return sync;
211   }
212   /**
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 }
227    */
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!");
235         return null;
236       }
237     });
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.
248     try {
249       while (instream.ready()) {
250         // TODO: mark objects in oobjhash prior to unmarshalling, to detect when objects have been lost through an update.
251         //tohere
252         Object obj = unmarshaller.unmarshal(ckedInstream);
253         boolean sync=ensure_references(unrefedObj, objrefs);
254         if (!(obj instanceof Vobject))
255           return null;
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};
258       }
259     } catch (MarshalException e) {
260       // TODO Auto-generated catch block
261       e.printStackTrace();
262     } catch (ValidationException e) {
263       // TODO Auto-generated catch block
264       e.printStackTrace();
265     } catch (IOException e) {
266       // TODO Auto-generated catch block
267       e.printStackTrace();
268     }
269     return null;
270   }
271 }