new marshaller instance ensures we use marshalling properties and correct validation...
[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.objects.core.VamsasDocument;
29 /**
30  * Implements the Vamsas Vobject ID machinery for translating 
31  * between non-volatile XML IDs and Vobject references. Use the
32  * marshalling and unmarshalling methods in this class in order
33  * to add automatically computed values for required fields in objects,
34  * so as to avoid validation exceptions when marshalling new objects
35  * into the vamsas document.
36  */
37 public class VorbaXmlBinder implements UnmarshalListener {
38   private static Log log = LogFactory.getLog(VorbaXmlBinder.class);
39   private final IVorbaIdFactory vorbafactory;
40
41   private final Vector obj;
42   private final Hashtable oldobjhashes;
43   private final Hashtable objrefs;
44   private final Vector updatedobjs; // not yet used elswhere ?
45   public VorbaXmlBinder(IVorbaIdFactory vorbafactory2, Vector unrefedObj, Hashtable objrefs2, Hashtable oldobjhashes, Vector updatedObj) {
46     this.vorbafactory = vorbafactory2;
47     this.obj = unrefedObj;
48     this.objrefs = objrefs2;
49     this.oldobjhashes = oldobjhashes;
50     this.updatedobjs = updatedObj;
51   }
52
53   /*
54    * (non-Javadoc)
55    * 
56    * @see org.exolab.castor.xml.UnmarshalListener#attributesProcessed(java.lang.Object)
57    */
58   public void attributesProcessed(Object object) {
59
60   }
61
62   /*
63    * (non-Javadoc)
64    * 
65    * @see org.exolab.castor.xml.UnmarshalListener#fieldAdded(java.lang.String,
66    *      java.lang.Object, java.lang.Object)
67    */
68   public void fieldAdded(String fieldName, Object parent, Object child) {
69     if (parent instanceof Vobject && child instanceof Vobject) {
70       if (((Vobject) child).V_parent==null) {
71         // System.err.println("Setting parent of "+fieldName);
72         ((Vobject) child).setV_parent((Vobject) parent);
73       }
74     }
75   }
76
77   /*
78    * (non-Javadoc)
79    * 
80    * @see org.exolab.castor.xml.UnmarshalListener#initialized(java.lang.Object)
81    */
82   public void initialized(Object object) {
83     if (object instanceof Vobject) {
84       Vobject nobj = (Vobject) object;
85     }
86   }
87
88   /*
89    * Check if the object has an 'id' field - if it does, copy the value into
90    * the VorbaId field of Vobject, and add the Vobject to the VorbaId hash.
91    * 
92    * @see org.exolab.castor.xml.UnmarshalListener#unmarshalled(java.lang.Object)
93    */
94   public void unmarshalled(Object newobj) {
95     if (newobj instanceof Vobject) {
96       Vobject nobj = (Vobject) newobj;
97       nobj.set__stored_in_document(true);
98       try {
99         if (nobj.isRegisterable() && nobj.___id_field!=null) {
100           VorbaId nobj_id=null;
101           // look for the id field (should be an NCName string)
102           nobj.__vorba = vorbafactory;
103           // use the Vobject accessor method to avoid unpleasant security exceptions.
104           String idstring = nobj.__getInstanceIdField();
105           if (idstring!=null) { 
106             if (idstring.length() > 0) {
107               nobj.setVorbaId(VorbaId.newId(idstring));
108               nobj_id=nobj.getVorbaId();
109               if (objrefs.containsKey(nobj_id) && !objrefs.get(nobj_id).equals(nobj)) {
110                 System.err.println("Serious problem : duplicate id '"+idstring+"' found! expect badness.");
111                 // TODO: HANDLE duplicate XML ids correctly
112               }
113               objrefs.put(nobj_id, nobj);
114             } else {
115               // add to list of objects without a valid vorbaId
116               obj.add(nobj);
117             }
118           } else {
119             // TODO: add to list of objects without a valid vorbaId
120             obj.add(nobj);
121           }
122           nobj.doHash(); // updates detected by comparing with last hash when marshalling
123           // check to see if new object was present in old object hash
124           if (nobj_id!=null) {
125             if (oldobjhashes.containsKey(nobj_id)) {
126               Vobjhash oldhash = (Vobjhash) oldobjhashes.get(nobj_id);
127               if (oldhash.isUpdated(nobj)) {
128                 // mark the object as updated in this document read.
129                 nobj.set__updated_since_last_read(true);
130                 updatedobjs.addElement(nobj);
131               }
132             } else {
133               // object has no entry in the oldhashvalue list but
134               // there is a valid vorbaId so
135               nobj.set__added_since_last_read(true);
136             }
137             // and record the just-unmarshalled hash value
138             oldobjhashes.put(nobj_id, new Vobjhash(nobj));
139           } else {
140             // for objects yet to get a valid vorba_id
141             nobj.set__added_since_last_read(true);
142           }
143         }
144
145       } catch (Exception e) {
146         return;
147       };
148
149     }
150   }
151
152   /**
153    * writes the VamsasDocument to the given stream.
154    * TODO: ensure that (at least) default provenance entries are written for objects.
155    * @param outstream
156    * @param vorba valid VorbaIdFactory to construct any missing IDs 
157    * @param doc
158    * @throws IOException
159    * @throws MarshalException
160    * @throws ValidationException
161    */
162   public static void putVamsasDocument(PrintWriter outstream, VorbaIdFactory vorba, VamsasDocument doc)
163   throws IOException, MarshalException, ValidationException {
164     // Ensure references
165     if (vorba==null)
166       throw new Error("Null VorbaIdFactory Parameter");
167     if (doc.__vorba==null)
168       doc.__vorba = vorba;
169     doc.__ensure_instance_ids(); // this may take a while. Do we allow for cyclic references ? 
170     Marshaller mshl = new Marshaller(outstream);
171     mshl.marshal(doc);
172
173   }
174   /**
175    * creates new VorbaId references where necessary for newly unmarshalled objects
176    * @param unrefed
177    * @param objrefs
178    * @return false if any new object references were made
179    */
180   private static boolean ensure_references(Vector unrefed, Hashtable objrefs) {
181     boolean sync=true;
182     if (unrefed.size()>0) {
183       sync=false; // document is out of sync - ids have been created.
184       java.util.Iterator newobj = unrefed.listIterator();
185       while (newobj.hasNext()) {
186         Vobject o = (Vobject) newobj.next();
187         // forces registration and id field update.
188         VorbaId id = o.getVorbaId();
189         if (!objrefs.containsKey(id)) {
190           objrefs.put(id, o);
191         } else {
192           if (!objrefs.get(id).equals(o))
193             throw new Error("Serious! Duplicate reference made by vorbaIdFactory!");
194         }
195       }
196     }
197     return sync;
198   }
199   /**
200    * Unmarshals a vamsasDocument Vobject from a stream, registers
201    * unregistered objects, records existing VorbaIds, and completes 
202    * the uk.ac.vamsas.client.Vobject housekeeping fields.
203    * For a valid unmarshalling, the array of returned objects also includes
204    * a <return>sync</return> parameter which is true if new VorbaIds
205    * were created. If sync is false, then the caller should ensure that the
206    * vamsasDocument is written back to disk to propagate the new VorbaIds.
207    *  TODO: ensure that provenance is correct for newly registered objects
208    * as getVamsasObjects but will detect updated objects based on differing hash values
209    * obtained from the VorbaIdFactory's VorbaId, Vobject.get__last_Hash() pairs (if any) 
210    * @param instream - the XML input stream 
211    * @param factory - the SimpleClient's properly configured VorbaId factory to make new references.
212    * @param root the root element's uk.ac.vamsas.objects.core Vobject.
213    * @return null or {(Object) VamsasDocument Vobject, (Object) Hashtable of Vobject references, (Object) Boolean(sync), (Object) Vector of updated objects in document }
214    */
215   public static Object[] getVamsasObjects(Reader instream,
216       VorbaIdFactory factory, Vobject root) {  
217     Unmarshaller unmarshaller = new Unmarshaller(root);
218     unmarshaller.setIDResolver(new IDResolver() {
219       public Object resolve(String id) {
220         // TODO: allow for external ID resolution
221         VorbaXmlBinder.log.warn("Warning - id " + id
222             + " is not found in the Vamsas XML! (TODO: Ignore if this is a forward reference!)");
223         return null;
224       }
225     });
226     final Hashtable objrefs = new Hashtable();
227     if (factory.extanthashv==null)
228       factory.extanthashv=new Hashtable();
229     final Hashtable oobjhashes=factory.extanthashv;
230     final VorbaIdFactory vorbafactory = factory;
231     final Vector unrefedObj =  new Vector();
232     final Vector updatedObj = new Vector();
233     unmarshaller.setUnmarshalListener(new VorbaXmlBinder(vorbafactory, unrefedObj, objrefs, oobjhashes,updatedObj));
234     // Call the unmarshaller.
235     try {
236       while (instream.ready()) {
237         // TODO: mark objects in oobjhash prior to unmarshalling, to detect when objects have been lost through an update.
238         //tohere
239         Object obj = unmarshaller.unmarshal(instream);
240         boolean sync=ensure_references(unrefedObj, objrefs);
241         if (!(obj instanceof Vobject))
242           return null;
243         vorbafactory.setNewIdHash(objrefs); // update the Document IO Handler's set of vorbaId<>Object bindings.
244         return new Object[] { obj, objrefs, new Boolean(sync),updatedObj};
245       }
246     } catch (MarshalException e) {
247       // TODO Auto-generated catch block
248       e.printStackTrace();
249     } catch (ValidationException e) {
250       // TODO Auto-generated catch block
251       e.printStackTrace();
252     } catch (IOException e) {
253       // TODO Auto-generated catch block
254       e.printStackTrace();
255     }
256     return null;
257   }
258 }