applied LGPLv3 and source code formatting.
[vamsas.git] / src / uk / ac / vamsas / client / VorbaXmlBinder.java
1 /*\r
2  * This file is part of the Vamsas Client version 0.1. \r
3  * Copyright 2009 by Jim Procter, Iain Milne, Pierre Marguerite, \r
4  *  Andrew Waterhouse and Dominik Lindner.\r
5  * \r
6  * Earlier versions have also been incorporated into Jalview version 2.4 \r
7  * since 2008, and TOPALi version 2 since 2007.\r
8  * \r
9  * The Vamsas Client is free software: you can redistribute it and/or modify\r
10  * it under the terms of the GNU Lesser General Public License as published by\r
11  * the Free Software Foundation, either version 3 of the License, or\r
12  * (at your option) any later version.\r
13  *  \r
14  * The Vamsas Client is distributed in the hope that it will be useful,\r
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
17  * GNU Lesser General Public License for more details.\r
18  * \r
19  * You should have received a copy of the GNU Lesser General Public License\r
20  * along with the Vamsas Client.  If not, see <http://www.gnu.org/licenses/>.\r
21  */\r
22 package uk.ac.vamsas.client;\r
23 \r
24 import java.io.IOException;\r
25 import java.io.PrintWriter;\r
26 import java.io.Reader;\r
27 import java.io.Writer;\r
28 import java.lang.reflect.Field;\r
29 import java.util.Hashtable;\r
30 import java.util.Iterator;\r
31 import java.util.Vector;\r
32 \r
33 import org.apache.commons.logging.Log;\r
34 import org.apache.commons.logging.LogFactory;\r
35 import org.exolab.castor.mapping.FieldHandler;\r
36 import org.exolab.castor.mapping.GeneralizedFieldHandler;\r
37 import org.exolab.castor.mapping.ValidityException;\r
38 import org.exolab.castor.xml.IDResolver;\r
39 import org.exolab.castor.xml.MarshalException;\r
40 import org.exolab.castor.xml.MarshalListener;\r
41 import org.exolab.castor.xml.Marshaller;\r
42 import org.exolab.castor.xml.UnmarshalListener;\r
43 import org.exolab.castor.xml.Unmarshaller;\r
44 import org.exolab.castor.xml.ValidationException;\r
45 \r
46 import uk.ac.vamsas.objects.core.VamsasDocument;\r
47 \r
48 /**\r
49  * Implements the Vamsas Vobject ID machinery for translating between\r
50  * non-volatile XML IDs and Vobject references. Use the marshalling and\r
51  * unmarshalling methods in this class in order to add automatically computed\r
52  * values for required fields in objects, so as to avoid validation exceptions\r
53  * when marshalling new objects into the vamsas document.\r
54  */\r
55 public class VorbaXmlBinder implements UnmarshalListener {\r
56   private static Log log = LogFactory.getLog(VorbaXmlBinder.class);\r
57 \r
58   private final IVorbaIdFactory vorbafactory;\r
59 \r
60   private final Vector obj;\r
61 \r
62   private final Hashtable oldobjhashes;\r
63 \r
64   private final Hashtable objrefs;\r
65 \r
66   private final Vector updatedobjs; // not yet used elswhere ?\r
67 \r
68   public VorbaXmlBinder(IVorbaIdFactory vorbafactory2, Vector unrefedObj,\r
69       Hashtable objrefs2, Hashtable oldobjhashes, Vector updatedObj) {\r
70     this.vorbafactory = vorbafactory2;\r
71     this.obj = unrefedObj;\r
72     this.objrefs = objrefs2;\r
73     this.oldobjhashes = oldobjhashes;\r
74     this.updatedobjs = updatedObj;\r
75   }\r
76 \r
77   /*\r
78    * (non-Javadoc)\r
79    * \r
80    * @see\r
81    * org.exolab.castor.xml.UnmarshalListener#attributesProcessed(java.lang.Object\r
82    * )\r
83    */\r
84   public void attributesProcessed(Object object) {\r
85 \r
86   }\r
87 \r
88   /*\r
89    * (non-Javadoc)\r
90    * \r
91    * @see org.exolab.castor.xml.UnmarshalListener#fieldAdded(java.lang.String,\r
92    * java.lang.Object, java.lang.Object)\r
93    */\r
94   public void fieldAdded(String fieldName, Object parent, Object child) {\r
95     if (parent instanceof Vobject && child instanceof Vobject) {\r
96       if (((Vobject) child).V_parent == null) {\r
97         // System.err.println("Setting parent of "+fieldName);\r
98         ((Vobject) child).setV_parent((Vobject) parent);\r
99       }\r
100     }\r
101   }\r
102 \r
103   /*\r
104    * (non-Javadoc)\r
105    * \r
106    * @see org.exolab.castor.xml.UnmarshalListener#initialized(java.lang.Object)\r
107    */\r
108   public void initialized(Object object) {\r
109     if (object instanceof Vobject) {\r
110       Vobject nobj = (Vobject) object;\r
111     }\r
112   }\r
113 \r
114   /*\r
115    * Check if the object has an 'id' field - if it does, copy the value into the\r
116    * VorbaId field of Vobject, and add the Vobject to the VorbaId hash.\r
117    * \r
118    * @see org.exolab.castor.xml.UnmarshalListener#unmarshalled(java.lang.Object)\r
119    */\r
120   public void unmarshalled(Object newobj) {\r
121     if (newobj instanceof Vobject) {\r
122       Vobject nobj = (Vobject) newobj;\r
123       nobj.set__stored_in_document(true);\r
124       try {\r
125         if (nobj.isRegisterable() && nobj.___id_field != null) {\r
126           VorbaId nobj_id = null;\r
127           // look for the id field (should be an NCName string)\r
128           nobj.__vorba = vorbafactory;\r
129           // use the Vobject accessor method to avoid unpleasant security\r
130           // exceptions.\r
131           String idstring = nobj.__getInstanceIdField();\r
132           if (idstring != null) {\r
133             if (idstring.length() > 0) {\r
134               nobj.setVorbaId(VorbaId.newId(idstring));\r
135               nobj_id = nobj.getVorbaId();\r
136               if (objrefs.containsKey(nobj_id)\r
137                   && !objrefs.get(nobj_id).equals(nobj)) {\r
138                 System.err.println("Serious problem : duplicate id '"\r
139                     + idstring + "' found! expect badness.");\r
140                 // TODO: HANDLE duplicate XML ids correctly\r
141               }\r
142               objrefs.put(nobj_id, nobj);\r
143             } else {\r
144               // add to list of objects without a valid vorbaId\r
145               obj.add(nobj);\r
146             }\r
147           } else {\r
148             // TODO: add to list of objects without a valid vorbaId\r
149             obj.add(nobj);\r
150           }\r
151           nobj.doHash(); // updates detected by comparing with last hash when\r
152                          // marshalling\r
153           // check to see if new object was present in old object hash\r
154           if (nobj_id != null) {\r
155             if (oldobjhashes.containsKey(nobj_id)) {\r
156               Vobjhash oldhash = (Vobjhash) oldobjhashes.get(nobj_id);\r
157               if (oldhash.isUpdated(nobj)) {\r
158                 // mark the object as updated in this document read.\r
159                 nobj.set__updated_since_last_read(true);\r
160                 updatedobjs.addElement(nobj);\r
161               }\r
162             } else {\r
163               // object has no entry in the oldhashvalue list but\r
164               // there is a valid vorbaId so\r
165               nobj.set__added_since_last_read(true);\r
166             }\r
167             // and record the just-unmarshalled hash value\r
168             oldobjhashes.put(nobj_id, new Vobjhash(nobj));\r
169           } else {\r
170             // for objects yet to get a valid vorba_id\r
171             nobj.set__added_since_last_read(true);\r
172           }\r
173         }\r
174 \r
175       } catch (Exception e) {\r
176         return;\r
177       }\r
178       ;\r
179 \r
180     }\r
181   }\r
182 \r
183   /**\r
184    * writes the VamsasDocument to the given stream. TODO: ensure that (at least)\r
185    * default provenance entries are written for objects.\r
186    * \r
187    * @param outstream\r
188    * @param vorba\r
189    *          valid VorbaIdFactory to construct any missing IDs\r
190    * @param doc\r
191    * @throws IOException\r
192    * @throws MarshalException\r
193    * @throws ValidationException\r
194    */\r
195   public static void putVamsasDocument(PrintWriter outstream,\r
196       VorbaIdFactory vorba, VamsasDocument doc) throws IOException,\r
197       MarshalException, ValidationException {\r
198     // Ensure references\r
199     if (vorba == null)\r
200       throw new Error("Null VorbaIdFactory Parameter");\r
201     if (doc.__vorba == null)\r
202       doc.__vorba = vorba;\r
203     doc.__ensure_instance_ids(); // this may take a while. Do we allow for\r
204                                  // cyclic references ?\r
205     Marshaller mshl = new Marshaller(outstream);\r
206     mshl.marshal(doc);\r
207 \r
208   }\r
209 \r
210   /**\r
211    * creates new VorbaId references where necessary for newly unmarshalled\r
212    * objects\r
213    * \r
214    * @param unrefed\r
215    * @param objrefs\r
216    * @return false if any new object references were made\r
217    */\r
218   private static boolean ensure_references(Vector unrefed, Hashtable objrefs) {\r
219     boolean sync = true;\r
220     if (unrefed.size() > 0) {\r
221       sync = false; // document is out of sync - ids have been created.\r
222       java.util.Iterator newobj = unrefed.listIterator();\r
223       while (newobj.hasNext()) {\r
224         Vobject o = (Vobject) newobj.next();\r
225         // forces registration and id field update.\r
226         VorbaId id = o.getVorbaId();\r
227         if (!objrefs.containsKey(id)) {\r
228           objrefs.put(id, o);\r
229         } else {\r
230           if (!objrefs.get(id).equals(o))\r
231             throw new Error(\r
232                 "Serious! Duplicate reference made by vorbaIdFactory!");\r
233         }\r
234       }\r
235     }\r
236     return sync;\r
237   }\r
238 \r
239   /**\r
240    * Unmarshals a vamsasDocument Vobject from a stream, registers unregistered\r
241    * objects, records existing VorbaIds, and completes the\r
242    * uk.ac.vamsas.client.Vobject housekeeping fields. For a valid unmarshalling,\r
243    * the array of returned objects also includes a <return>sync</return>\r
244    * parameter which is true if new VorbaIds were created. If sync is false,\r
245    * then the caller should ensure that the vamsasDocument is written back to\r
246    * disk to propagate the new VorbaIds. TODO: ensure that provenance is correct\r
247    * for newly registered objects as getVamsasObjects but will detect updated\r
248    * objects based on differing hash values obtained from the VorbaIdFactory's\r
249    * VorbaId, Vobject.get__last_Hash() pairs (if any)\r
250    * \r
251    * @param instream\r
252    *          - the XML input stream\r
253    * @param factory\r
254    *          - the SimpleClient's properly configured VorbaId factory to make\r
255    *          new references.\r
256    * @param root\r
257    *          the root element's uk.ac.vamsas.objects.core Vobject.\r
258    * @return null or {(Object) VamsasDocument Vobject, (Object) Hashtable of\r
259    *         Vobject references, (Object) Boolean(sync), (Object) Vector of\r
260    *         updated objects in document }\r
261    */\r
262   public static Object[] getVamsasObjects(Reader instream,\r
263       VorbaIdFactory factory, Vobject root) {\r
264     Unmarshaller unmarshaller = new Unmarshaller(root);\r
265     unmarshaller.setIDResolver(new IDResolver() {\r
266       public Object resolve(String id) {\r
267         // TODO: allow for external ID resolution\r
268         VorbaXmlBinder.log\r
269             .warn("Warning - id "\r
270                 + id\r
271                 + " is not found in the Vamsas XML! (TODO: Ignore if this is a forward reference!)");\r
272         return null;\r
273       }\r
274     });\r
275     final Hashtable objrefs = new Hashtable();\r
276     if (factory.extanthashv == null)\r
277       factory.extanthashv = new Hashtable();\r
278     final Hashtable oobjhashes = factory.extanthashv;\r
279     final VorbaIdFactory vorbafactory = factory;\r
280     final Vector unrefedObj = new Vector();\r
281     final Vector updatedObj = new Vector();\r
282     unmarshaller.setUnmarshalListener(new VorbaXmlBinder(vorbafactory,\r
283         unrefedObj, objrefs, oobjhashes, updatedObj));\r
284     // Call the unmarshaller.\r
285     try {\r
286       while (instream.ready()) {\r
287         // TODO: mark objects in oobjhash prior to unmarshalling, to detect when\r
288         // objects have been lost through an update.\r
289         // tohere\r
290         Object obj = unmarshaller.unmarshal(instream);\r
291         boolean sync = ensure_references(unrefedObj, objrefs);\r
292         if (!(obj instanceof Vobject))\r
293           return null;\r
294         vorbafactory.setNewIdHash(objrefs); // update the Document IO Handler's\r
295                                             // set of vorbaId<>Object bindings.\r
296         return new Object[] { obj, objrefs, new Boolean(sync), updatedObj };\r
297       }\r
298     } catch (MarshalException e) {\r
299       // TODO Auto-generated catch block\r
300       e.printStackTrace();\r
301     } catch (ValidationException e) {\r
302       // TODO Auto-generated catch block\r
303       e.printStackTrace();\r
304     } catch (IOException e) {\r
305       // TODO Auto-generated catch block\r
306       e.printStackTrace();\r
307     }\r
308     return null;\r
309   }\r
310 }\r