2 * This file is part of the Vamsas Client version 0.2.
\r
3 * Copyright 2010 by Jim Procter, Iain Milne, Pierre Marguerite,
\r
4 * Andrew Waterhouse and Dominik Lindner.
\r
6 * Earlier versions have also been incorporated into Jalview version 2.4
\r
7 * since 2008, and TOPALi version 2 since 2007.
\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
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
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
22 package uk.ac.vamsas.client;
\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
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
46 import uk.ac.vamsas.objects.core.VamsasDocument;
\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
55 public class VorbaXmlBinder implements UnmarshalListener {
\r
56 private static Log log = LogFactory.getLog(VorbaXmlBinder.class);
\r
58 private final IVorbaIdFactory vorbafactory;
\r
60 private final Vector obj;
\r
62 private final Hashtable oldobjhashes;
\r
64 private final Hashtable objrefs;
\r
66 private final Vector updatedobjs; // not yet used elswhere ?
\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
81 * org.exolab.castor.xml.UnmarshalListener#attributesProcessed(java.lang.Object
\r
84 public void attributesProcessed(Object object) {
\r
91 * @see org.exolab.castor.xml.UnmarshalListener#fieldAdded(java.lang.String,
\r
92 * java.lang.Object, java.lang.Object)
\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
106 * @see org.exolab.castor.xml.UnmarshalListener#initialized(java.lang.Object)
\r
108 public void initialized(Object object) {
\r
109 if (object instanceof Vobject) {
\r
110 Vobject nobj = (Vobject) object;
\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
118 * @see org.exolab.castor.xml.UnmarshalListener#unmarshalled(java.lang.Object)
\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
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
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
142 objrefs.put(nobj_id, nobj);
\r
144 // add to list of objects without a valid vorbaId
\r
148 // TODO: add to list of objects without a valid vorbaId
\r
151 nobj.doHash(); // updates detected by comparing with last hash when
\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
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
167 // and record the just-unmarshalled hash value
\r
168 oldobjhashes.put(nobj_id, new Vobjhash(nobj));
\r
170 // for objects yet to get a valid vorba_id
\r
171 nobj.set__added_since_last_read(true);
\r
175 } catch (Exception e) {
\r
184 * writes the VamsasDocument to the given stream. TODO: ensure that (at least)
\r
185 * default provenance entries are written for objects.
\r
189 * valid VorbaIdFactory to construct any missing IDs
\r
191 * @throws IOException
\r
192 * @throws MarshalException
\r
193 * @throws ValidationException
\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
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
211 * creates new VorbaId references where necessary for newly unmarshalled
\r
216 * @return false if any new object references were made
\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
230 if (!objrefs.get(id).equals(o))
\r
232 "Serious! Duplicate reference made by vorbaIdFactory!");
\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
252 * - the XML input stream
\r
254 * - the SimpleClient's properly configured VorbaId factory to make
\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
262 public static Object[] getVamsasObjects(Reader instream,
\r
263 VorbaIdFactory factory, Vobject root) {
\r
264 Unmarshaller unmarshaller = new Unmarshaller(root);
\r
265 final VorbaIdFactory ourfactory = factory;
\r
266 unmarshaller.setIDResolver(new IDResolver() {
\r
267 public Object resolve(String id) {
\r
268 if (ourfactory.warnUnresolved) {
\r
269 // TODO: allow for external ID resolution
\r
271 .warn("Warning - id "
\r
273 + " is not found in the Vamsas XML! (TODO: Ignore if this is a forward reference!)");
\r
278 final Hashtable objrefs = new Hashtable();
\r
279 if (factory.extanthashv == null)
\r
280 factory.extanthashv = new Hashtable();
\r
281 final Hashtable oobjhashes = factory.extanthashv;
\r
282 final VorbaIdFactory vorbafactory = factory;
\r
283 final Vector unrefedObj = new Vector();
\r
284 final Vector updatedObj = new Vector();
\r
285 unmarshaller.setUnmarshalListener(new VorbaXmlBinder(vorbafactory,
\r
286 unrefedObj, objrefs, oobjhashes, updatedObj));
\r
287 // Call the unmarshaller.
\r
289 while (instream.ready()) {
\r
290 // TODO: mark objects in oobjhash prior to unmarshalling, to detect when
\r
291 // objects have been lost through an update.
\r
293 factory.warnUnresolved = false;
\r
294 Object obj = unmarshaller.unmarshal(instream);
\r
295 factory.warnUnresolved = true;
\r
296 boolean sync = ensure_references(unrefedObj, objrefs);
\r
297 if (!(obj instanceof Vobject))
\r
299 vorbafactory.setNewIdHash(objrefs); // update the Document IO Handler's
\r
300 // set of vorbaId<>Object bindings.
\r
301 return new Object[] { obj, objrefs, new Boolean(sync), updatedObj };
\r
303 } catch (MarshalException e) {
\r
304 // TODO Auto-generated catch block
\r
305 e.printStackTrace();
\r
306 } catch (ValidationException e) {
\r
307 // TODO Auto-generated catch block
\r
308 e.printStackTrace();
\r
309 } catch (IOException e) {
\r
310 // TODO Auto-generated catch block
\r
311 e.printStackTrace();
\r