KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectstyle > cayenne > xml > XMLDecoder


1 /* ====================================================================
2  *
3  * The ObjectStyle Group Software License, version 1.1
4  * ObjectStyle Group - http://objectstyle.org/
5  *
6  * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
7  * of the software. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution, if any,
22  * must include the following acknowlegement:
23  * "This product includes software developed by independent contributors
24  * and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
25  * Alternately, this acknowlegement may appear in the software itself,
26  * if and wherever such third-party acknowlegements normally appear.
27  *
28  * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
29  * or promote products derived from this software without prior written
30  * permission. For written permission, email
31  * "andrus at objectstyle dot org".
32  *
33  * 5. Products derived from this software may not be called "ObjectStyle"
34  * or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
35  * names without prior written permission.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
41  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  * ====================================================================
50  *
51  * This software consists of voluntary contributions made by many
52  * individuals and hosted on ObjectStyle Group web site. For more
53  * information on the ObjectStyle Group, please see
54  * <http://objectstyle.org/>.
55  */

56
57 package org.objectstyle.cayenne.xml;
58
59 import java.io.Reader JavaDoc;
60 import java.util.ArrayList JavaDoc;
61 import java.util.Collection JavaDoc;
62 import java.util.HashMap JavaDoc;
63 import java.util.Iterator JavaDoc;
64 import java.util.List JavaDoc;
65 import java.util.Map JavaDoc;
66
67 import org.apache.commons.beanutils.ConstructorUtils;
68 import org.apache.commons.beanutils.PropertyUtils;
69 import org.jdom.Document;
70 import org.jdom.Element;
71 import org.jdom.input.SAXBuilder;
72 import org.objectstyle.cayenne.CayenneRuntimeException;
73 import org.objectstyle.cayenne.DataObject;
74 import org.objectstyle.cayenne.access.DataContext;
75
76 /**
77  * XMLDecoder is used to decode XML into objects.
78  *
79  * @author Kevin J. Menard, Jr.
80  * @since 1.2
81  */

82 public class XMLDecoder {
83
84     static final Map JavaDoc classMapping = new HashMap JavaDoc();
85
86     static {
87         classMapping.put("boolean", Boolean JavaDoc.class);
88         classMapping.put("int", Integer JavaDoc.class);
89         classMapping.put("char", Character JavaDoc.class);
90         classMapping.put("float", Float JavaDoc.class);
91         classMapping.put("byte", Byte JavaDoc.class);
92         classMapping.put("short", Short JavaDoc.class);
93         classMapping.put("long", Long JavaDoc.class);
94         classMapping.put("double", Double JavaDoc.class);
95     }
96
97     /** The root of the XML document being decoded. */
98     private Element root;
99
100     /** The data context to register decoded DataObjects with. */
101     private DataContext dc;
102
103     // TODO: H to the A to the C to the K
104
private List JavaDoc decodedCollections = new ArrayList JavaDoc();
105
106     /**
107      * Default constructor. This will create an XMLDecoder instance that will decode
108      * objects from XML, but will not register them with any DataContext.
109      *
110      * @see XMLDecoder#XMLDecoder(DataContext)
111      */

112     public XMLDecoder() {
113         this(null);
114     }
115
116     /**
117      * Creates an XMLDecoder that will register decoded DataObjects with the specified
118      * DataContext.
119      *
120      * @param dc The DataContext to register decoded DataObjects with.
121      */

122     public XMLDecoder(DataContext dc) {
123         this.dc = dc;
124     }
125
126     /**
127      * Decodes an XML element to a Boolean.
128      *
129      * @param xmlTag The tag identifying the element.
130      * @return The tag's value.
131      */

132     public Boolean JavaDoc decodeBoolean(String JavaDoc xmlTag) {
133         String JavaDoc val = decodeString(xmlTag);
134
135         if (null == val) {
136             return null;
137         }
138
139         return Boolean.valueOf(val);
140     }
141
142     /**
143      * Decodes an XML element to a Double.
144      *
145      * @param xmlTag The tag identifying the element.
146      * @return The tag's value.
147      */

148     public Double JavaDoc decodeDouble(String JavaDoc xmlTag) {
149         String JavaDoc val = decodeString(xmlTag);
150
151         if (null == val) {
152             return null;
153         }
154
155         return Double.valueOf(val);
156     }
157
158     /**
159      * Decodes an XML element to a Float.
160      *
161      * @param xmlTag The tag identifying the element.
162      * @return The tag's value.
163      */

164     public Float JavaDoc decodeFloat(String JavaDoc xmlTag) {
165         String JavaDoc val = decodeString(xmlTag);
166
167         if (null == val) {
168             return null;
169         }
170
171         return Float.valueOf(val);
172     }
173
174     /**
175      * Decodes an XML element to an Integer.
176      *
177      * @param xmlTag The tag identifying the element.
178      * @return The tag's value.
179      */

180     public Integer JavaDoc decodeInteger(String JavaDoc xmlTag) {
181         String JavaDoc val = decodeString(xmlTag);
182
183         if (null == val) {
184             return null;
185         }
186
187         return Integer.valueOf(val);
188     }
189
190     /**
191      * Decodes an object from XML.
192      *
193      * @param xmlTag The XML tag corresponding to the root of the encoded object.
194      * @return The decoded object.
195      */

196     public Object JavaDoc decodeObject(String JavaDoc xmlTag) {
197         // Find the XML element corresponding to the supplied tag.
198
Element child = root.getChild(xmlTag);
199
200         return decodeObject(child);
201     }
202
203     /**
204      * Decodes an XML element to an Object.
205      *
206      * @param child The XML element.
207      * @return The tag's value.
208      */

209     private Object JavaDoc decodeObject(Element child) {
210
211         if (null == child) {
212             return null;
213         }
214
215         String JavaDoc type = child.getAttributeValue("type");
216         if (null == type) {
217             // TODO should we use String by default? Or guess from the property type?
218
throw new CayenneRuntimeException("No type specified for tag '"
219                     + child.getName()
220                     + "'.");
221         }
222
223         // temp hack to support primitives...
224
Class JavaDoc objectClass = (Class JavaDoc) classMapping.get(type);
225         if (null == objectClass) {
226             try {
227                 objectClass = Class.forName(type);
228             }
229             catch (Exception JavaDoc e) {
230                 throw new CayenneRuntimeException("Unrecognized class '"
231                         + objectClass
232                         + "'", e);
233             }
234         }
235
236         try {
237             // This crazy conditional checks if we're decoding a collection. There are two ways
238
// to enter into this body:
239
// 1) If there are two elements at the same level with the same name, then they should
240
// part of a collection.
241
// 2) If a single occurring element has the "forceList" attribute set to "YES", then it
242
// too should be treated as a collection.
243
//
244
// The final part checks that we have not previously attempted to decode this collection,
245
// which is necessary to prevent infinite loops .
246
if ((((null != child.getParentElement()) && (child
247                     .getParentElement()
248                     .getChildren(child.getName())
249                     .size() > 1)) || ((null != child.getAttributeValue("forceList")) && (child
250                     .getAttributeValue("forceList")
251                     .toUpperCase().equals("YES"))))
252                     && (false == decodedCollections.contains(child))) {
253                 return decodeCollection(child);
254             }
255
256             // If the object implements XMLSerializable, delegate decoding to the class's
257
// implementation of decodeFromXML().
258
else if (XMLSerializable.class.isAssignableFrom(objectClass)) {
259                 XMLSerializable ret = (XMLSerializable) objectClass.newInstance();
260                 ret.decodeFromXML(this);
261
262                 return ret;
263             }
264
265             // If we hit here, then we should be encoding "simple" properties, which are basically
266
// objects that take a single arg String constructor.
267
else if (ConstructorUtils.getAccessibleConstructor(objectClass, String JavaDoc.class) != null) {
268                 // Create a new object of the type supplied as the "type" attribute
269
// in the XML element that
270
// represents the XML element's text value.
271
// E.g., for <count type="java.lang.Integer">13</count>, this is
272
// equivalent to new Integer("13");
273
return ConstructorUtils.invokeConstructor(objectClass, child.getText());
274             }
275
276             // If we hit here, then we're trying to decode something we're not equipped to handle.
277
// E.g., a complex object that does not implement XMLSerializable.
278
else {
279                 throw new CayenneRuntimeException(
280                         "Error decoding tag '"
281                                 + child.getName()
282                                 + "': "
283                                 + "specified class does not have a constructor taking either a String or an XMLDecoder");
284             }
285         }
286         catch (Exception JavaDoc e) {
287             throw new CayenneRuntimeException("Error decoding tag '"
288                     + child.getName()
289                     + "'", e);
290         }
291     }
292
293     /**
294      * Decodes an XML element to a String.
295      *
296      * @param xmlTag The tag identifying the element.
297      * @return The tag's value.
298      */

299     public String JavaDoc decodeString(String JavaDoc xmlTag) {
300         // Find the XML element corresponding to the supplied tag, and simply
301
// return its text.
302
return root.getChildText(xmlTag);
303     }
304
305     /**
306      * Decodes XML wrapped by a Reader into an object.
307      *
308      * @param xml Wrapped XML.
309      * @return A new instance of the object represented by the XML.
310      * @throws CayenneRuntimeException
311      */

312     public Object JavaDoc decode(Reader JavaDoc xml) throws CayenneRuntimeException {
313
314         // Parse the XML into a JDOM representation.
315
Document data = parse(xml);
316
317         // Delegate to the decode() method that works on JDOM elements.
318
return decodeElement(data.getRootElement());
319     }
320
321     /**
322      * Decodes XML wrapped by a Reader into an object, using the supplied mapping file to
323      * guide the decoding process.
324      *
325      * @param xml Wrapped XML.
326      * @param mappingUrl Mapping file describing how the XML elements and object
327      * properties correlate.
328      * @return A new instance of the object represented by the XML.
329      * @throws CayenneRuntimeException
330      * @see XMLMappingUtil#decode(Element)
331      */

332     public Object JavaDoc decode(Reader JavaDoc xml, String JavaDoc mappingUrl) throws CayenneRuntimeException {
333         // Parse the XML document into a JDOM representation.
334
Document data = parse(xml);
335
336         // MappingUtils will really do all the work.
337
XMLMappingUtil mu = new XMLMappingUtil(mappingUrl);
338         Object JavaDoc ret = mu.decode(data.getRootElement());
339
340         if (null != dc) {
341             dc.registerNewObject((DataObject) ret);
342         }
343
344         return ret;
345     }
346
347     /**
348      * Decodes the XML element to an object. If the supplied DataContext is not null, the
349      * object will be registered with it and committed to the database.
350      *
351      * @param element The XML element.
352      * @return The decoded object.
353      * @throws CayenneRuntimeException
354      */

355     private Object JavaDoc decodeElement(Element element) throws CayenneRuntimeException {
356
357         // Update root to be the supplied xml element. This is necessary as
358
// root is used for decoding properties.
359
Element oldRoot = root;
360         root = element;
361
362         // Create the object we're ultimately returning. It is represented
363
// by the root element of the XML.
364
Object JavaDoc object;
365
366         try {
367             object = decodeObject(element);
368         }
369         catch (Throwable JavaDoc th) {
370             throw new CayenneRuntimeException("Error instantiating object", th);
371         }
372
373         if ((null != dc) && (object instanceof DataObject)) {
374             dc.registerNewObject((DataObject) object);
375         }
376
377         root = oldRoot;
378         decodedCollections.clear();
379
380         return object;
381     }
382
383     /**
384      * Decodes a Collection represented by XML wrapped by a Reader into a List of objects.
385      * Each object will be registered with the supplied DataContext.
386      *
387      * @param xml The XML element representing the elements in the collection to decode.
388      * @return A List of all the decoded objects.
389      * @throws CayenneRuntimeException
390      */

391     private Collection JavaDoc decodeCollection(Element xml) throws CayenneRuntimeException {
392
393         Collection JavaDoc ret;
394         try {
395             String JavaDoc parentClass = xml.getParentElement().getAttributeValue("type");
396             Object JavaDoc property = Class.forName(parentClass).newInstance();
397             Collection JavaDoc c = (Collection JavaDoc) PropertyUtils.getNestedProperty(property, xml
398                     .getName());
399
400             ret = (Collection JavaDoc) c.getClass().newInstance();
401         }
402         catch (Exception JavaDoc ex) {
403             throw new CayenneRuntimeException(
404                     "Could not create collection with no-arg constructor.",
405                     ex);
406         }
407
408         // Each child of the root corresponds to an XML representation of
409
// the object. The idea is decode each of those into an object and add them to the
410
// list to be returned.
411
for (Iterator JavaDoc it = xml.getParentElement().getChildren(xml.getName()).iterator(); it
412                 .hasNext();) {
413             // Decode the object.
414
Element e = (Element) it.next();
415             decodedCollections.add(e);
416             Object JavaDoc o = decodeElement(e);
417
418             // Add it to the output list.
419
ret.add(o);
420         }
421
422         return ret;
423     }
424
425     /**
426      * Decodes a list of objects. This intended to be used to decode a list of encdoded
427      * DataObjects.
428      *
429      * @param xml The wrapped XML encoding of the list of objects.
430      * @return The list of decoded objects.
431      */

432     public List JavaDoc decodeList(Reader JavaDoc xml) {
433         return decodeList(xml, null);
434     }
435
436     /**
437      * Decodes a list of objects using the specified mapping. This intended to be used to
438      * decode a list of encdoded DataObjects.
439      *
440      * @param xml The wrapped XML encoding of the list of objects.
441      * @param mappingUrl The mapping file that defines how the list elements should be
442      * decoded.
443      * @return The list of decoded objects.
444      * @throws CayenneRuntimeException
445      */

446     public List JavaDoc decodeList(Reader JavaDoc xml, String JavaDoc mappingUrl) throws CayenneRuntimeException {
447         Element listRoot = parse(xml).getRootElement();
448
449         List JavaDoc ret;
450         try {
451             String JavaDoc parentClass = listRoot.getAttributeValue("type");
452             ret = (List JavaDoc) Class.forName(parentClass).newInstance();
453         }
454         catch (Exception JavaDoc ex) {
455             throw new CayenneRuntimeException(
456                     "Could not create collection with no-arg constructor.",
457                     ex);
458         }
459
460         XMLMappingUtil mu = null;
461         if (null != mappingUrl) {
462             mu = new XMLMappingUtil(mappingUrl);
463         }
464
465         // Each child of the root corresponds to an XML representation of
466
// the object. The idea is decode each of those into an object and add them to the
467
// list to be returned.
468
for (Iterator JavaDoc it = listRoot.getChildren().iterator(); it.hasNext();) {
469             // Decode the object.
470
Element e = (Element) it.next();
471             decodedCollections.add(e);
472             Object JavaDoc o;
473
474             // Decode the item using the appropriate decoding method.
475
if (null == mu) {
476                 o = decodeElement(e);
477             }
478             else {
479                 o = mu.decode(e);
480             }
481
482             // Register the decoded object with the data context if necessary.
483
if ((null != dc) && (o instanceof DataObject)) {
484                 dc.registerNewObject((DataObject) o);
485             }
486
487             // Add it to the output list.
488
ret.add(o);
489         }
490
491         return ret;
492     }
493
494     /**
495      * Takes the XML wrapped in a Reader and returns a JDOM Document representation of it.
496      *
497      * @param in Wrapped XML.
498      * @return JDOM Document wrapping the XML for use throughout the rest of the decoder.
499      * @throws CayenneRuntimeException
500      */

501     private Document parse(Reader JavaDoc in) throws CayenneRuntimeException {
502
503         // Read in the XML file holding the data to be constructed into an
504
// object.
505
SAXBuilder parser = new SAXBuilder();
506
507         try {
508             return parser.build(in);
509         }
510         catch (Exception JavaDoc ex) {
511             throw new CayenneRuntimeException("Error parsing XML", ex);
512         }
513     }
514 }
Popular Tags