KickJava   Java API By Example, From Geeks To Geeks.

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


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.util.ArrayList JavaDoc;
60 import java.util.Collection JavaDoc;
61 import java.util.Collections 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 import java.util.Set JavaDoc;
67
68 import org.apache.commons.beanutils.BeanUtils;
69 import org.apache.commons.beanutils.PropertyUtils;
70 import org.jdom.Document;
71 import org.jdom.Element;
72 import org.jdom.input.SAXBuilder;
73 import org.objectstyle.cayenne.CayenneRuntimeException;
74
75 /**
76  * A convenience class for dealing with the mapping file. This can encode and decode
77  * objects based upon the schema given by the map file.
78  *
79  * @author Kevin J. Menard, Jr.
80  * @since 1.2
81  */

82 final class XMLMappingUtil {
83
84     /** The root of the mapping file (the "model" tag). */
85     protected Element root;
86
87     /** Cached copies of entity items. */
88     protected Map JavaDoc entities;
89
90     /**
91      * Creates a MappingUtils instance using a URL that points to the mapping file.
92      *
93      * @param mappingUrl A URL to the mapping file that specifies the mapping model.
94      * @throws CayenneRuntimeException
95      */

96     public XMLMappingUtil(String JavaDoc mappingUrl) throws CayenneRuntimeException {
97
98         // Read in the mapping file.
99
SAXBuilder parser = new SAXBuilder();
100         Document mapping;
101
102         try {
103             mapping = parser.build(mappingUrl);
104         }
105         catch (Exception JavaDoc ex) {
106             throw new CayenneRuntimeException("Error parsing XML", ex);
107         }
108
109         setRoot(mapping.getRootElement());
110     }
111
112     /**
113      * Sets the root of the mapping document.
114      *
115      * @param root The root node of the mapping document.
116      */

117     private void setRoot(Element root) {
118
119         if (!"model".equals(root.getName())) {
120             throw new CayenneRuntimeException(
121                     "Root of the mapping model must be \"model\"");
122         }
123
124         this.root = root;
125
126         entities = new HashMap JavaDoc();
127         for (Iterator JavaDoc it = getEntities().iterator(); it.hasNext();) {
128             Element e = (Element) it.next();
129
130             entities.put(e.getAttributeValue("xmlTag"), e);
131         }
132     }
133
134     /**
135      * Returns a safe copy of the entities list.
136      *
137      * @return The list of entities.
138      */

139     private List JavaDoc getEntities() {
140         return Collections.unmodifiableList(root.getChildren());
141     }
142
143     /**
144      * Returns a safe copy of the entity names set.
145      *
146      * @return The set of entity names.
147      */

148     private Set JavaDoc getEntityNames() {
149         return Collections.unmodifiableSet(entities.keySet());
150     }
151
152     /**
153      * Returns the "root" entity. This entity represents the object to ultimately be
154      * created.
155      *
156      * @return The "root" entity.
157      */

158     // TODO Decide whether this should be called "primary" entity.
159
private Element getRootEntity() {
160         return (Element) getEntities().get(0);
161     }
162
163     /**
164      * Encodes an entity to XML.
165      *
166      * @param object The object to be encoded by this entity block.
167      * @param entity The entity block in XML (from the mapping file).
168      * @return The encoded entity.
169      * @throws CayenneRuntimeException
170      */

171     private Element encodeEntity(Object JavaDoc object, Element entity)
172             throws CayenneRuntimeException {
173
174         // Create the xml item to return.
175
Element ret = new Element(entity.getAttributeValue("xmlTag"));
176
177         // Each of the entity's children will correspond to a child in the returned
178
// item.
179
for (Iterator JavaDoc it = entity.getChildren().iterator(); it.hasNext();) {
180             Element property = (Element) it.next();
181             String JavaDoc xmlTag = property.getAttributeValue("xmlTag");
182             String JavaDoc propertyName = property.getAttributeValue("name");
183
184             Object JavaDoc propertyValue = getProperty(object, propertyName);
185
186             // If the child refers to an entity, skip over it, since when that entity
187
// is processed, it will
188
// generate the appropriate xml items.
189
if (getEntityNames().contains(xmlTag) == false) {
190                 // Otherwise, create a new child for the returned xml item, encoding
191
// the passed in object's property.
192

193                 XMLEncoder encoder = new XMLEncoder();
194
195                 // TODO This will not work on a collection. We need to first pull out
196
// the object and then encode its property.
197
encoder.encodeProperty(xmlTag, propertyValue);
198                 Element e = encoder.getRoot();
199                 e.removeAttribute("type");
200
201                 // Once the property is encoded, add it to the item to be returned.
202
ret.addContent(e);
203             }
204             else {
205
206                 if (propertyValue instanceof Collection JavaDoc) {
207
208                     List JavaDoc encodedObjects = encodeCollection(
209                             (Collection JavaDoc) propertyValue,
210                             getEntity(xmlTag));
211
212                     for (Iterator JavaDoc it2 = encodedObjects.iterator(); it2.hasNext();) {
213                         Element element = (Element) it2.next();
214                         ret.addContent(element);
215                     }
216                 }
217                 else {
218                     ret.addContent(encodeEntity(propertyValue, getEntity(xmlTag)));
219                 }
220             }
221         }
222
223         return ret;
224     }
225
226     /**
227      * Utility method for encoding a collection of objects.
228      *
229      * @param collection The collection to encode.
230      * @param entity The entity block corresponding to the collection's elements.
231      * @return A flat list of the encoded objects (i.e., there is no root node).
232      * @throws CayenneRuntimeException
233      */

234     private List JavaDoc encodeCollection(Collection JavaDoc collection, Element entity)
235             throws CayenneRuntimeException {
236
237         List JavaDoc ret = new ArrayList JavaDoc();
238
239         // For each element in the collection.
240
for (Iterator JavaDoc it = collection.iterator(); it.hasNext();) {
241             Object JavaDoc element = it.next();
242
243             ret.add(encodeEntity(element, entity));
244         }
245
246         return ret;
247     }
248
249     /**
250      * Encodes the object to XML. This object should correspond to the object specified in
251      * the "root" entity in the mapping file.
252      *
253      * @param object The object to be encoded.
254      * @return The XML document containing the encoded object.
255      * @throws CayenneRuntimeException
256      */

257     public Element encode(Object JavaDoc object) throws CayenneRuntimeException {
258         return encodeEntity(object, getRootEntity());
259     }
260
261     /**
262      * Returns the entity XML block with the same "xmlTag" value as the passed in name.
263      *
264      * @param name The name of the entity to retrieve.
265      * @return The entity with "xmlTag" equal to the passed in name.
266      */

267     private Element getEntity(String JavaDoc name) {
268         return (Element) entities.get(name);
269     }
270
271     /**
272      * Returns the property name that is associated with the passed in entity name. This
273      * is used to determine what property an entity block represents.
274      *
275      * @param rootEntity The root to which the reference to find is relative to.
276      * @param ref The name of the entity.
277      * @return The name of the property that is associated with the named entity.
278      */

279     // TODO Decide whether to change the @param tag description since "the name of the
280
// entity" can be deceiving.
281
private String JavaDoc getEntityRef(Element rootEntity, String JavaDoc ref) {
282         for (Iterator JavaDoc it = rootEntity.getChildren().iterator(); it.hasNext();) {
283             Element child = (Element) it.next();
284
285             if (child.getAttributeValue("xmlTag").equals(ref)) {
286                 return child.getAttributeValue("name");
287             }
288         }
289
290         return null;
291     }
292
293     /**
294      * Decodes a property.
295      *
296      * @param object The object to be updated with the decoded property's value.
297      * @param entity The entity block that contains the property mapping for the value.
298      * @param encProperty The encoded property.
299      * @throws CayenneRuntimeException
300      */

301     private void decodeProperty(Object JavaDoc object, Element entity, Element encProperty)
302             throws CayenneRuntimeException {
303
304         List JavaDoc children = encProperty.getChildren();
305         String JavaDoc xmlTag = encProperty.getName();
306
307         // This is a "simple" encoded property. Find the associated property mapping
308
// in the entity.
309
if (children.isEmpty()) {
310             // Scan each of the entity's property mappings to see if any of them
311
// correspond to the passed in xmlTag.
312
for (Iterator JavaDoc it = entity.getChildren().iterator(); it.hasNext();) {
313                 Element e = (Element) it.next();
314
315                 // If the property mapping is found . . .
316
if (e.getAttributeValue("xmlTag").equals(xmlTag)) {
317
318                     // use it to determine the actual property to be setting in the
319
// object.
320
setProperty(object, e.getAttributeValue("name"), encProperty
321                             .getText());
322                 }
323             }
324         }
325
326         // If the property has children, then it corresponds to a "helper" entity,
327
// which corresponds to an embedded object.
328
else {
329             // Create the embedded object.
330
Object JavaDoc o = newInstance(getEntity(xmlTag).getAttributeValue("name"));
331
332             // Decode each of the property's children, setting values in the newly
333
// created object.
334
for (Iterator JavaDoc it = children.iterator(); it.hasNext();) {
335                 Element child = (Element) it.next();
336
337                 decodeProperty(o, getEntity(xmlTag), child);
338             }
339
340             // Set the property in the main object that corresponds to the newly
341
// created object.
342
Object JavaDoc property = getProperty(object, getEntityRef(entity, xmlTag));
343
344             if (property instanceof Collection JavaDoc) {
345                 Collection JavaDoc c = (Collection JavaDoc) property;
346                 c.add(o);
347             }
348             else {
349                 setProperty(object, getEntityRef(entity, xmlTag), o);
350             }
351         }
352     }
353
354     /**
355      * Decode the supplied XML JDOM document into an object.
356      *
357      * @param xml The JDOM document containing the encoded object.
358      * @return The decoded object.
359      * @throws CayenneRuntimeException
360      */

361     public Object JavaDoc decode(Element xml) throws CayenneRuntimeException {
362
363         // TODO: Add an error check to make sure the mapping file actually is for this
364
// data file.
365
List JavaDoc values = xml.getChildren();
366
367         // Create the object to be returned.
368
Object JavaDoc ret = newInstance(getRootEntity().getAttributeValue("name"));
369
370         // We want to read each value from the XML file and then set the corresponding
371
// property value in the object to be returned.
372
for (Iterator JavaDoc it = values.iterator(); it.hasNext();) {
373             Element value = (Element) it.next();
374
375             decodeProperty(ret, getRootEntity(), value);
376         }
377
378         return ret;
379     }
380
381     /**
382      * Sets object property, wrapping any exceptions in CayenneRuntimeException.
383      *
384      * @param object The object to set the property value on.
385      * @param property The name of the property to set.
386      * @param value The value of the property.
387      * @throws CayenneRuntimeException
388      */

389     private void setProperty(Object JavaDoc object, String JavaDoc property, Object JavaDoc value)
390             throws CayenneRuntimeException {
391         try {
392             BeanUtils.setProperty(object, property, value);
393         }
394         catch (Exception JavaDoc ex) {
395             throw new CayenneRuntimeException("Error setting property " + property, ex);
396         }
397     }
398
399     /**
400      * Returns the value for an object's property.
401      *
402      * @param object The object whose property value to retrieve.
403      * @param property The property whose value to retrieve.
404      * @return The retrieved property value.
405      * @throws CayenneRuntimeException
406      */

407     private Object JavaDoc getProperty(Object JavaDoc object, String JavaDoc property)
408             throws CayenneRuntimeException {
409         try {
410             return PropertyUtils.getNestedProperty(object, property);
411         }
412         catch (Exception JavaDoc ex) {
413             throw new CayenneRuntimeException("Error reading property '"
414                     + property
415                     + "'.", ex);
416         }
417     }
418
419     /**
420      * Instantiates a new object for class name, wrapping any exceptions in
421      * CayenneRuntimeException.
422      *
423      * @param className The type to create a new instance of.
424      * @return The newly created object.
425      * @throws CayenneRuntimeException
426      */

427     private Object JavaDoc newInstance(String JavaDoc className) throws CayenneRuntimeException {
428         try {
429             return Class.forName(className).newInstance();
430         }
431         catch (Exception JavaDoc ex) {
432             throw new CayenneRuntimeException("Error creating instance of class "
433                     + className, ex);
434         }
435     }
436 }
Popular Tags