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