ec6c7f4e7442a899718a34a21f70794d2428680e
[vamsas.git] / src / org / vamsas / client / VorbaXmlBinder.java
1 /**
2  * 
3  */
4 package org.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.exolab.castor.mapping.FieldHandler;
16 import org.exolab.castor.mapping.GeneralizedFieldHandler;
17 import org.exolab.castor.mapping.ValidityException;
18 import org.exolab.castor.xml.IDResolver;
19 import org.exolab.castor.xml.MarshalException;
20 import org.exolab.castor.xml.MarshalListener;
21 import org.exolab.castor.xml.Marshaller;
22 import org.exolab.castor.xml.UnmarshalListener;
23 import org.exolab.castor.xml.Unmarshaller;
24 import org.exolab.castor.xml.ValidationException;
25 import org.vamsas.objects.core.VamsasDocument;
26 /**
27  * Implements the Vamsas object ID machinery for translating 
28  * between non-volatile XML IDs and object references. Use the
29  * marshalling and unmarshalling methods in this class in order
30  * to avoid validation exceptions when marshalling new objects
31  * into the vamsas document.
32  */
33 public class VorbaXmlBinder extends GeneralizedFieldHandler implements UnmarshalListener, MarshalListener {
34   private final IVorbaIdFactory vorbafactory;
35
36   private final Vector obj;
37
38   private final Hashtable objrefs;
39
40   public VorbaXmlBinder(IVorbaIdFactory vorbafactory, Vector obj, Hashtable objrefs) {
41     this.vorbafactory = vorbafactory;
42     this.obj = obj;
43     this.objrefs = objrefs;
44   }
45
46   /*
47    * (non-Javadoc)
48    * 
49    * @see org.exolab.castor.xml.UnmarshalListener#attributesProcessed(java.lang.Object)
50    */
51   public void attributesProcessed(Object object) {
52   }
53
54   /*
55    * (non-Javadoc)
56    * 
57    * @see org.exolab.castor.xml.UnmarshalListener#fieldAdded(java.lang.String,
58    *      java.lang.Object, java.lang.Object)
59    */
60   public void fieldAdded(String fieldName, Object parent, Object child) {
61   }
62
63   /*
64    * (non-Javadoc)
65    * 
66    * @see org.exolab.castor.xml.UnmarshalListener#initialized(java.lang.Object)
67    */
68   public void initialized(Object object) {
69   }
70   
71   /*
72    * Check if the object has an 'id' field - if it does, copy the value into
73    * the VorbaId field of object, and add the object to the VorbaId hash.
74    * 
75    * @see org.exolab.castor.xml.UnmarshalListener#unmarshalled(java.lang.Object)
76    */
77   public void unmarshalled(Object newobj) {
78     if (newobj instanceof object) {
79       object nobj = (object) newobj;
80       nobj.set__stored_in_document(true);
81       Field fd = null;
82       try {
83         if (nobj.isRegisterable()) {
84           // look for the id field (should be an NCName string)
85           nobj.__vorba = vorbafactory;
86           fd = nobj.getClass().getField("id");
87           String idstring;
88           if (fd.get(nobj) != null) {
89             idstring = (String) fd.get(nobj);
90             if (idstring.length() > 0) {
91               if (!objrefs.containsKey(idstring)) {
92                 objrefs.put(idstring, nobj);
93                 nobj.setVorbaId(VorbaId.newId(idstring));
94               } else {
95                 System.err.println("Serious problem : duplicate id '"+idstring+"' found! expect badness.");
96                 // TODO: HANDLE duplicate XML ids correctly
97               }
98             } else {
99               // add to list of objects without a valid vorbaId
100               obj.add(nobj);
101             }
102           } else {
103             // add to list of objects without a valid vorbaId
104             obj.add(nobj);
105           }
106           
107         nobj.doHash();
108       }
109       } catch (Exception e) {
110         return;
111       };
112       
113     }
114   }
115
116   /**
117    * writes the VamsasDocument to the given stream.
118    * TODO: ensure that (at least) default provenance entries are written for objects.
119    * @param outstream
120    * @param vorba valid VorbaIdFactory to construct any missing IDs 
121    * @param doc
122    * @throws IOException
123    * @throws MarshalException
124    * @throws ValidationException
125    */
126   public static void putVamsasDocument(PrintWriter outstream, VorbaIdFactory vorba, VamsasDocument doc)
127       throws IOException, MarshalException, ValidationException {
128     // Ensure references
129     if (vorba==null)
130       throw new Error("Null VorbaIdVactory Parameter");
131     if (doc.__vorba==null)
132       doc.__vorba = vorba;
133     doc.__ensure_instance_ids(); // this may take a while. Do we allow for cyclic references ? 
134     final Vector refstomake = new Vector();
135     VorbaXmlBinder binder = new VorbaXmlBinder(vorba, refstomake, vorba.extantids);
136     Marshaller marshaller = new Marshaller(outstream);
137     marshaller.setMarshalAsDocument(true);
138     marshaller.setMarshalListener(binder);
139     marshaller.marshal(doc);
140   }
141
142   private static boolean ensure_references(Vector unrefed, Hashtable objrefs) {
143     boolean sync=true;
144     if (unrefed.size()>0) {
145       sync=false; // document is out of sync - ids have been created.
146       java.util.Iterator newobj = unrefed.listIterator();
147       while (newobj.hasNext()) {
148         object o = (object) newobj.next();
149         // forces registration and id field update.
150         VorbaId id = o.getVorbaId();
151         if (!objrefs.containsKey(id)) {
152           objrefs.put(id.id, o);
153         } else {
154           throw new Error("Serious! Duplicate reference made by vorbaIdFactory!");
155         }
156       }
157     }
158     return sync;
159   }
160   /**
161      * Unmarshals a vamsasDocument object from a stream, registers
162      * unregistered objects, records existing VorbaIds, and completes 
163      * the org.vamsas.client.object housekeeping fields.
164      * For a valid unmarshalling, the array of returned objects also includes
165      * a <return>sync</return> parameter which is true if new VorbaIds
166      * were created. If sync is false, then the caller should ensure that the
167      * vamsasDocument is written back to disk to propagate the new VorbaIds.
168      *  TODO: ensure that provenance is correct for newly registered objects
169      * @param instream - the XML input stream 
170    * @param factory - the SimpleClient's properly configured VorbaId factory to make new references.
171    * @param root the root element's org.vamsas.objects.core object.
172      * @return null or {(Object) VamsasDocument object, (Object) Hashtable of object references, (Object) Boolean(sync) }
173      */
174   public static Object[] getVamsasObjects(Reader instream,
175         IVorbaIdFactory factory, object root) {
176       Unmarshaller unmarshaller = new Unmarshaller(root);
177       unmarshaller.setIDResolver(new IDResolver() {
178         public Object resolve(String id) {
179           System.err.println("Warning - id " + id
180               + " is not found in the VamsasDocument!");
181           return null;
182         }
183       });
184       Hashtable refbase = new Hashtable();
185       Vector unrefed = new Vector();
186       final Hashtable objrefs = refbase;
187       final IVorbaIdFactory vorbafactory = factory;
188       final Vector unrefedObj =  unrefed;
189       unmarshaller.setUnmarshalListener(new VorbaXmlBinder(vorbafactory, unrefedObj, objrefs));
190       // Call the unmarshaller.
191       try {
192         while (instream.ready()) {
193           Object obj = unmarshaller.unmarshal(instream);
194           boolean sync=ensure_references(unrefed, objrefs);
195           if (!(obj instanceof object))
196             return null;
197           return new Object[] { obj, objrefs, new Boolean(sync)};
198           }
199       } catch (MarshalException e) {
200         // TODO Auto-generated catch block
201         e.printStackTrace();
202       } catch (ValidationException e) {
203         // TODO Auto-generated catch block
204         e.printStackTrace();
205       } catch (IOException e) {
206         // TODO Auto-generated catch block
207         e.printStackTrace();
208       }
209       return null;
210     }
211
212   /* (non-Javadoc)
213    * @see org.exolab.castor.xml.MarshalListener#postMarshal(java.lang.Object)
214    */
215   public void postMarshal(Object object) {
216     // TODO Auto-generated method stub
217     
218   }
219
220   /* (non-Javadoc)
221    * @see org.exolab.castor.xml.MarshalListener#preMarshal(java.lang.Object)
222    */
223   public boolean preMarshal(Object newobj) {
224     if (newobj instanceof object) {
225       object nobj = (object) newobj;
226       nobj.set__stored_in_document(true);
227       Field fd = null;
228       try {
229         if (nobj.isRegisterable()) {
230           // make sure the id field is set
231           nobj.__vorba = vorbafactory;
232           fd = nobj.getClass().getField("_id");
233           if (fd.get(nobj) != null) {
234             fd.set(nobj, nobj.getVorbaId().getId());
235             /* all thats needed perhaps
236              * 
237              *if (idstring.length() > 0) {
238               if (!objrefs.containsKey(idstring)) {
239                 objrefs.put(idstring, nobj);
240                 nobj.setVorbaId(VorbaId.newId(idstring));
241               } else {
242                 System.err.println("Serious problem : duplicate id '"+idstring+"' found! expect badness.");
243                 return false; // TODO: HANDLE duplicate XML ids correctly
244               }*/
245           }
246         }
247       } catch (Exception e) {
248         return false;
249       };
250       
251     }
252     return false;
253   }
254
255   /* (non-Javadoc)
256    * @see org.exolab.castor.mapping.GeneralizedFieldHandler#convertUponGet(java.lang.Object)
257    */
258   public Object convertUponGet(Object value) {
259     // TODO Auto-generated method stub
260     return null;
261   }
262
263   /* (non-Javadoc)
264    * @see org.exolab.castor.mapping.GeneralizedFieldHandler#convertUponSet(java.lang.Object)
265    */
266   public Object convertUponSet(Object value) {
267     // TODO Auto-generated method stub
268     return null;
269   }
270
271   /* (non-Javadoc)
272    * @see org.exolab.castor.mapping.GeneralizedFieldHandler#getFieldType()
273    */
274   public Class getFieldType() {
275     // TODO Auto-generated method stub
276     return null;
277   }
278
279   /* (non-Javadoc)
280    * @see org.exolab.castor.mapping.GeneralizedFieldHandler#newInstance(java.lang.Object, java.lang.Object[])
281    */
282   public Object newInstance(Object parent, Object[] args) throws IllegalStateException {
283     // TODO Auto-generated method stub
284     return super.newInstance(parent, args);
285   }
286
287   /* (non-Javadoc)
288    * @see org.exolab.castor.mapping.GeneralizedFieldHandler#newInstance(java.lang.Object)
289    */
290   public Object newInstance(Object parent) throws IllegalStateException {
291     // TODO Auto-generated method stub
292     return super.newInstance(parent);
293   }
294
295   /* (non-Javadoc)
296    * @see org.exolab.castor.mapping.GeneralizedFieldHandler#setCollectionIteration(boolean)
297    */
298   public void setCollectionIteration(boolean autoCollectionIteration) {
299     // TODO Auto-generated method stub
300     super.setCollectionIteration(autoCollectionIteration);
301   }
302
303   /* (non-Javadoc)
304    * @see org.exolab.castor.mapping.AbstractFieldHandler#hasValue(java.lang.Object)
305    */
306   public boolean hasValue(Object object) {
307     // TODO Auto-generated method stub
308     return super.hasValue(object);
309   }
310 }