KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*****************************************************************
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  ****************************************************************/

19
20
21 package org.apache.cayenne.xml;
22
23 import java.io.Reader JavaDoc;
24 import java.lang.reflect.Constructor JavaDoc;
25 import java.text.ParseException JavaDoc;
26 import java.text.SimpleDateFormat JavaDoc;
27 import java.util.ArrayList JavaDoc;
28 import java.util.Collection JavaDoc;
29 import java.util.Date JavaDoc;
30 import java.util.HashMap JavaDoc;
31 import java.util.Iterator JavaDoc;
32 import java.util.List JavaDoc;
33 import java.util.Map JavaDoc;
34
35 import javax.xml.parsers.DocumentBuilder JavaDoc;
36
37 import org.apache.cayenne.CayenneRuntimeException;
38 import org.apache.cayenne.Persistent;
39 import org.apache.cayenne.access.DataContext;
40 import org.apache.cayenne.reflect.PropertyUtils;
41 import org.apache.cayenne.util.Util;
42 import org.w3c.dom.Document JavaDoc;
43 import org.w3c.dom.Element JavaDoc;
44 import org.xml.sax.InputSource JavaDoc;
45
46 /**
47  * XMLDecoder is used to decode XML into objects.
48  *
49  * @author Kevin J. Menard, Jr.
50  * @since 1.2
51  */

52 public class XMLDecoder {
53
54     static final Map JavaDoc classMapping = new HashMap JavaDoc();
55
56     static {
57         classMapping.put("boolean", Boolean JavaDoc.class);
58         classMapping.put("int", Integer JavaDoc.class);
59         classMapping.put("char", Character JavaDoc.class);
60         classMapping.put("float", Float JavaDoc.class);
61         classMapping.put("byte", Byte JavaDoc.class);
62         classMapping.put("short", Short JavaDoc.class);
63         classMapping.put("long", Long JavaDoc.class);
64         classMapping.put("double", Double JavaDoc.class);
65     }
66
67     /** The root of the XML document being decoded. */
68     private Element JavaDoc root;
69
70     /** The data context to register decoded DataObjects with. */
71     private DataContext dataContext;
72
73     // TODO: H to the A to the C to the K
74
private List JavaDoc decodedCollections = new ArrayList JavaDoc();
75
76     /**
77      * Default constructor. This will create an XMLDecoder instance that will decode
78      * objects from XML, but will not register them with any DataContext.
79      *
80      * @see XMLDecoder#XMLDecoder(DataContext)
81      */

82     public XMLDecoder() {
83         this(null);
84     }
85
86     /**
87      * Creates an XMLDecoder that will register decoded DataObjects with the specified
88      * DataContext.
89      *
90      * @param dc The DataContext to register decoded DataObjects with.
91      */

92     public XMLDecoder(DataContext dc) {
93         this.dataContext = dc;
94     }
95
96     /**
97      * Decodes an XML element to a Boolean.
98      *
99      * @param xmlTag The tag identifying the element.
100      * @return The tag's value.
101      */

102     public Boolean JavaDoc decodeBoolean(String JavaDoc xmlTag) {
103         String JavaDoc val = decodeString(xmlTag);
104
105         if (null == val) {
106             return null;
107         }
108
109         return Boolean.valueOf(val);
110     }
111
112     /**
113      * Decodes an XML element to a Double.
114      *
115      * @param xmlTag The tag identifying the element.
116      * @return The tag's value.
117      */

118     public Double JavaDoc decodeDouble(String JavaDoc xmlTag) {
119         String JavaDoc val = decodeString(xmlTag);
120
121         if (null == val) {
122             return null;
123         }
124
125         return Double.valueOf(val);
126     }
127
128     /**
129      * Decodes an XML element to a Float.
130      *
131      * @param xmlTag The tag identifying the element.
132      * @return The tag's value.
133      */

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

150     public Integer JavaDoc decodeInteger(String JavaDoc xmlTag) {
151         String JavaDoc val = decodeString(xmlTag);
152
153         if (null == val) {
154             return null;
155         }
156
157         return Integer.valueOf(val);
158     }
159
160     /**
161      * Decodes an object from XML.
162      *
163      * @param xmlTag The XML tag corresponding to the root of the encoded object.
164      * @return The decoded object.
165      */

166     public Object JavaDoc decodeObject(String JavaDoc xmlTag) {
167         // Find the XML element corresponding to the supplied tag.
168
Element JavaDoc child = XMLUtil.getChild(root, xmlTag);
169
170         return decodeObject(child);
171     }
172
173     /**
174      * Decodes an XML element to an Object.
175      *
176      * @param child The XML element.
177      * @return The tag's value.
178      */

179     private Object JavaDoc decodeObject(Element JavaDoc child) {
180
181         if (null == child) {
182             return null;
183         }
184
185         String JavaDoc type = child.getAttribute("type");
186         if (Util.isEmptyString(type)) {
187             // TODO should we use String by default? Or guess from the property type?
188
throw new CayenneRuntimeException("No type specified for tag '"
189                     + child.getNodeName()
190                     + "'.");
191         }
192
193         // temp hack to support primitives...
194
Class JavaDoc objectClass = (Class JavaDoc) classMapping.get(type);
195         if (null == objectClass) {
196             try {
197                 objectClass = Class.forName(type);
198             }
199             catch (Exception JavaDoc e) {
200                 throw new CayenneRuntimeException("Unrecognized class '"
201                         + objectClass
202                         + "'", e);
203             }
204         }
205
206         try {
207             // This crazy conditional checks if we're decoding a collection. There are two
208
// ways to enter into this body:
209
// 1) If there are two elements at the same level with the same name, then
210
// they should be part of a collection.
211
// 2) If a single occurring element has the "forceList" attribute set to
212
// "YES", then it too should be treated as a collection.
213
//
214
// The final part checks that we have not previously attempted to decode this
215
// collection, which is necessary to prevent infinite loops .
216
if ((((null != child.getParentNode()) && (XMLUtil.getChildren(
217                     child.getParentNode(),
218                     child.getNodeName()).size() > 1)) || (child
219                     .getAttribute("forceList")
220                     .toUpperCase().equals("YES")))
221                     && (!decodedCollections.contains(child))) {
222                 return decodeCollection(child);
223             }
224
225             // If the object implements XMLSerializable, delegate decoding to the class's
226
// implementation of decodeFromXML().
227
if (XMLSerializable.class.isAssignableFrom(objectClass)) {
228                 // Fix for decoding 1-to-1 relationships between the same class type, per CAY-597.
229
// If we don't re-root the tree, the decoder goes into an infinite loop. In particular,
230
// if R1 -> R2, when it decodes R1, it will attempt to decode R2, but without re-rooting,
231
// the decoder tries to decode R1 again, think it's decoding R2, because R1 is the first
232
// element of that type found in the XML doc with the true root of the doc.
233
Element JavaDoc oldRoot = root;
234                 root = child;
235                 
236                 XMLSerializable ret = (XMLSerializable) objectClass.newInstance();
237                 ret.decodeFromXML(this);
238
239                 // Restore the root when we're done decoding the child.
240
root = oldRoot;
241                 
242                 return ret;
243             }
244             
245             String JavaDoc text = XMLUtil.getText(child);
246
247             // handle dates using hardcoded format....
248
if (Date JavaDoc.class.isAssignableFrom(objectClass)) {
249                 try {
250                     return new SimpleDateFormat JavaDoc(XMLUtil.DEFAULT_DATE_FORMAT).parse(text);
251                 }
252                 catch (ParseException JavaDoc e) {
253                     // handle pre-3.0 default data format for backwards compatibilty
254

255                     try {
256                         return new SimpleDateFormat JavaDoc("E MMM dd hh:mm:ss z yyyy").parse(text);
257                     }
258                     catch (ParseException JavaDoc eOld) {
259                         
260                         // rethrow the original exception
261
throw e;
262                     }
263                 }
264             }
265
266             // handle all other primitive types...
267
Constructor JavaDoc c = objectClass.getConstructor(new Class JavaDoc[] {
268                 String JavaDoc.class
269             });
270             return c.newInstance(new Object JavaDoc[] {
271                 text
272             });
273         }
274         catch (Exception JavaDoc e) {
275             throw new CayenneRuntimeException("Error decoding tag '"
276                     + child.getNodeName()
277                     + "'", e);
278         }
279     }
280
281     /**
282      * Decodes an XML element to a String.
283      *
284      * @param xmlTag The tag identifying the element.
285      * @return The tag's value.
286      */

287     public String JavaDoc decodeString(String JavaDoc xmlTag) {
288         // Find the XML element corresponding to the supplied tag, and simply
289
// return its text.
290
Element JavaDoc child = XMLUtil.getChild(root, xmlTag);
291         return child != null ? XMLUtil.getText(child) : null;
292     }
293
294     /**
295      * Decodes XML wrapped by a Reader into an object.
296      *
297      * @param xml Wrapped XML.
298      * @return A new instance of the object represented by the XML.
299      * @throws CayenneRuntimeException
300      */

301     public Object JavaDoc decode(Reader JavaDoc xml) throws CayenneRuntimeException {
302
303         // Parse the XML into a JDOM representation.
304
Document JavaDoc data = parse(xml);
305
306         // Delegate to the decode() method that works on JDOM elements.
307
return decodeElement(data.getDocumentElement());
308     }
309
310     /**
311      * Decodes XML wrapped by a Reader into an object, using the supplied mapping file to
312      * guide the decoding process.
313      *
314      * @param xml Wrapped XML.
315      * @param mappingUrl Mapping file describing how the XML elements and object
316      * properties correlate.
317      * @return A new instance of the object represented by the XML.
318      * @throws CayenneRuntimeException
319      */

320     public Object JavaDoc decode(Reader JavaDoc xml, String JavaDoc mappingUrl) throws CayenneRuntimeException {
321         // Parse the XML document into a JDOM representation.
322
Document JavaDoc data = parse(xml);
323
324         // MappingUtils will really do all the work.
325
XMLMappingDescriptor mu = new XMLMappingDescriptor(mappingUrl);
326
327         Object JavaDoc ret = mu.decode(data.getDocumentElement(), dataContext);
328
329         return ret;
330     }
331
332     /**
333      * Decodes the XML element to an object. If the supplied DataContext is not null, the
334      * object will be registered with it and committed to the database.
335      *
336      * @param element The XML element.
337      * @return The decoded object.
338      * @throws CayenneRuntimeException
339      */

340     private Object JavaDoc decodeElement(Element JavaDoc element) throws CayenneRuntimeException {
341
342         // Update root to be the supplied xml element. This is necessary as
343
// root is used for decoding properties.
344
Element JavaDoc oldRoot = root;
345         root = element;
346
347         // Create the object we're ultimately returning. It is represented
348
// by the root element of the XML.
349
Object JavaDoc object;
350
351         try {
352             object = decodeObject(element);
353         }
354         catch (Throwable JavaDoc th) {
355             throw new CayenneRuntimeException("Error instantiating object", th);
356         }
357
358         if ((null != dataContext) && (object instanceof Persistent)) {
359             dataContext.registerNewObject((Persistent) object);
360         }
361
362         root = oldRoot;
363         decodedCollections.clear();
364
365         return object;
366     }
367
368     /**
369      * Decodes a Collection represented by XML wrapped by a Reader into a List of objects.
370      * Each object will be registered with the supplied DataContext.
371      *
372      * @param xml The XML element representing the elements in the collection to decode.
373      * @return A List of all the decoded objects.
374      * @throws CayenneRuntimeException
375      */

376     private Collection JavaDoc decodeCollection(Element JavaDoc xml) throws CayenneRuntimeException {
377
378         Collection JavaDoc ret;
379         try {
380             String JavaDoc parentClass = ((Element JavaDoc) xml.getParentNode()).getAttribute("type");
381             Object JavaDoc property = Class.forName(parentClass).newInstance();
382             Collection JavaDoc c = (Collection JavaDoc) PropertyUtils.getProperty(property, xml
383                     .getNodeName());
384
385             ret = (Collection JavaDoc) c.getClass().newInstance();
386         }
387         catch (Exception JavaDoc ex) {
388             throw new CayenneRuntimeException(
389                     "Could not create collection with no-arg constructor.",
390                     ex);
391         }
392
393         // Each child of the root corresponds to an XML representation of
394
// the object. The idea is decode each of those into an object and add them to the
395
// list to be returned.
396
Iterator JavaDoc it = XMLUtil
397                 .getChildren(xml.getParentNode(), xml.getNodeName())
398                 .iterator();
399         while (it.hasNext()) {
400             // Decode the object.
401
Element JavaDoc e = (Element JavaDoc) it.next();
402             decodedCollections.add(e);
403             Object JavaDoc o = decodeElement(e);
404
405             // Add it to the output list.
406
ret.add(o);
407         }
408
409         return ret;
410     }
411
412     /**
413      * Decodes a list of DataObjects.
414      *
415      * @param xml The wrapped XML encoding of the list of DataObjects.
416      * @return The list of decoded DataObjects.
417      * @throws CayenneRuntimeException
418      */

419     public static List JavaDoc decodeList(Reader JavaDoc xml) throws CayenneRuntimeException {
420         return decodeList(xml, null, null);
421     }
422
423     /**
424      * Decodes a list of DataObjects, registering them the supplied DataContext.
425      *
426      * @param xml The wrapped XML encoding of the list of DataObjects.
427      * @param dc The DataContext to register the decode DataObjects with.
428      * @return The list of decoded DataObjects.
429      * @throws CayenneRuntimeException
430      */

431     public static List JavaDoc decodeList(Reader JavaDoc xml, DataContext dc)
432             throws CayenneRuntimeException {
433         return decodeList(xml, null, dc);
434     }
435
436     /**
437      * Decodes a list of DataObjects using the supplied mapping file to guide the decoding
438      * process.
439      *
440      * @param xml The wrapped XML encoding of the list of DataObjects.
441      * @param mappingUrl Mapping file describing how the XML elements and object
442      * properties correlate.
443      * @return The list of decoded DataObjects.
444      * @throws CayenneRuntimeException
445      */

446     public static List JavaDoc decodeList(Reader JavaDoc xml, String JavaDoc mappingUrl)
447             throws CayenneRuntimeException {
448         return decodeList(xml, mappingUrl, null);
449     }
450
451     /**
452      * Decodes a list of DataObjects using the supplied mapping file to guide the decoding
453      * process, registering them the supplied DataContext.
454      *
455      * @param xml The wrapped XML encoding of the list of objects.
456      * @param mappingUrl Mapping file describing how the XML elements and object
457      * properties correlate.
458      * @param dataContext The DataContext to register the decode DataObjects with.
459      * @return The list of decoded DataObjects.
460      * @throws CayenneRuntimeException
461      */

462     public static List JavaDoc decodeList(Reader JavaDoc xml, String JavaDoc mappingUrl, DataContext dataContext)
463             throws CayenneRuntimeException {
464
465         XMLDecoder decoder = new XMLDecoder(dataContext);
466         Element JavaDoc listRoot = parse(xml).getDocumentElement();
467
468         List JavaDoc ret;
469         try {
470             String JavaDoc parentClass = listRoot.getAttribute("type");
471             ret = (List JavaDoc) Class.forName(parentClass).newInstance();
472         }
473         catch (Exception JavaDoc ex) {
474             throw new CayenneRuntimeException(
475                     "Could not create collection with no-arg constructor.",
476                     ex);
477         }
478
479         XMLMappingDescriptor mu = null;
480         if (mappingUrl != null) {
481             mu = new XMLMappingDescriptor(mappingUrl);
482         }
483
484         // Each child of the root corresponds to an XML representation of
485
// the object. The idea is decode each of those into an object and add them to the
486
// list to be returned.
487
Iterator JavaDoc it = XMLUtil.getChildren(listRoot).iterator();
488         while (it.hasNext()) {
489             // Decode the object.
490
Element JavaDoc e = (Element JavaDoc) it.next();
491             decoder.decodedCollections.add(e);
492
493             // Decode the item using the appropriate decoding method.
494
Object JavaDoc o;
495
496             if (mu != null) {
497                 o = mu.decode(e, dataContext);
498             }
499             else {
500                 // decoder will do DataContext registration if needed.
501
o = decoder.decodeElement(e);
502             }
503
504             ret.add(o);
505         }
506
507         return ret;
508     }
509
510     /**
511      * Takes the XML wrapped in a Reader and returns a JDOM Document representation of it.
512      *
513      * @param in Wrapped XML.
514      * @return DOM Document wrapping the XML for use throughout the rest of the decoder.
515      * @throws CayenneRuntimeException
516      */

517     private static Document JavaDoc parse(Reader JavaDoc in) throws CayenneRuntimeException {
518         DocumentBuilder JavaDoc builder = XMLUtil.newBuilder();
519
520         try {
521             return builder.parse(new InputSource JavaDoc(in));
522         }
523         catch (Exception JavaDoc ex) {
524             throw new CayenneRuntimeException("Error parsing XML", ex);
525         }
526     }
527 }
528
Popular Tags