KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > enhydra > zeus > Unmarshaller


1 /*
2  * Enhydra Java Application Server Project
3  *
4  * The contents of this file are subject to the Enhydra Public License
5  * Version 1.1 (the "License"); you may not use this file except in
6  * compliance with the License. You may obtain a copy of the License on
7  * the Enhydra web site ( http://www.enhydra.org/ ).
8  *
9  * Software distributed under the License is distributed on an "AS IS"
10  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
11  * the License for the specific terms governing rights and limitations
12  * under the License.
13  *
14  * The Initial Developer of the Enhydra Application Server is Lutris
15  * Technologies, Inc. The Enhydra Application Server and portions created
16  * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
17  * All Rights Reserved.
18  */

19 package org.enhydra.zeus;
20
21 import java.io.IOException JavaDoc;
22 import java.lang.reflect.Method JavaDoc;
23 import java.lang.reflect.InvocationTargetException JavaDoc;
24 import java.util.HashMap JavaDoc;
25 import java.util.Iterator JavaDoc;
26 import java.util.List JavaDoc;
27 import java.util.Map JavaDoc;
28
29 // Zeus imports
30
import org.enhydra.zeus.util.ClassUtils;
31 import org.enhydra.zeus.util.NamingUtils;
32
33 // JDOM imports
34
import org.jdom.Attribute;
35 import org.jdom.Document;
36 import org.jdom.DocType;
37 import org.jdom.Element;
38 import org.jdom.JDOMException;
39 import org.jdom.Namespace;
40 import org.jdom.input.SAXBuilder;
41
42 /**
43  * <p>
44  * <code>Unmarshaller</code> takes an XML instance document, which should
45  * conform to some set of XML constraints, and creates a Java object from
46  * the XML document. This object is an instance of the implementation class,
47  * which in turn implements the interface, created using an implementation of
48  * <code>{@link Generator}</code>.
49  * </p>
50  * <p>
51  * It also assumes that the generated interface and implementation classes
52  * are in the classpath, as it will perform a <code>Class.forName()</code> on
53  * the classes.
54  * </p>
55  *
56  * @author Brett McLaughlin
57  * @author Maciej Zawadzki
58  */

59 public class Unmarshaller {
60     
61     /** Constant used to signal whether debug messages should be printed out */
62     protected boolean debug = false;
63     
64     /** Store interface/implementation class mappings */
65     private Map JavaDoc implClasses;
66
67     /** The package name of the interface/implementation classes */
68     private String JavaDoc javaPackage;
69
70     /**
71      * Prefix to be prepended to the name of every generated
72      * interface and class.
73      */

74     protected String JavaDoc namePrefix;
75
76     /**
77      * <p>
78      * Simple constructor.
79      * </p>
80      */

81     public Unmarshaller() {
82         implClasses = new HashMap JavaDoc();
83         namePrefix = "";
84     }
85     
86     /**
87      * <p>
88      * For the supplied interface name, this will allow a customized
89      * implementation class to be set. This effectively allows the user to
90      * specify their own implementation class to use instead of the default,
91      * Zeus-generated class.
92      * </p>
93      *
94      * @param interfaceName <code>String</code> name of interface being
95      * deal with.
96      * @param implClassName <code>String</code> name of class to load when
97      * implementations of <code>interfaceName</code> are needed.
98      */

99     public void setImplClass(String JavaDoc interfaceName, String JavaDoc implClassName) {
100         implClasses.put(interfaceName, implClassName);
101     }
102     
103     /**
104      * <p>
105      * This method allows the developer to set a prefix that
106      * will be prepended to the name of every generated
107      * interface and class.
108      * </p>
109      *
110      * @param String prefix that will be prepended to the
111      * name of every generated interface and class
112      */

113     public void setNamePrefix(String JavaDoc namePrefix) {
114         if (namePrefix != null) {
115             this.namePrefix = namePrefix;
116         }
117     }
118
119     /**
120      * <p>
121      * This method allows the developer to get the prefix that
122      * will be prepended to the name of every generated
123      * interface and class. Returns an empty string in the
124      * case that a prefix is not prepended.
125      * </p>
126      *
127      * @return <code>String</code> prefix that will be prepended
128      * to the name of every generated interface and class
129      */

130     public String JavaDoc getNamePrefix() {
131         return namePrefix;
132     }
133     
134     /**
135      * <p>
136      * For the supplied interface name, this will return the current
137      * implementation class associated with the interface. If no class has
138      * been specified, this will return the default, Zeus-generated class
139      * name for the interface.
140      * </p>
141      *
142      * @param interfaceName <code>String</code> name of interface being
143      * dealt with.
144      * @return <code>String</code> - implementation class for supplied
145      * <code>interfaceName</code>.
146      */

147     public String JavaDoc getImplClass(String JavaDoc interfaceName) {
148         String JavaDoc implClass = (String JavaDoc)implClasses.get(interfaceName);
149         if (implClass == null) {
150             // Construct class name for Zeus binding class
151
implClass = new StringBuffer JavaDoc()
152                 .append(namePrefix)
153                 .append(interfaceName)
154                 .append("Impl")
155                 .toString();
156         }
157         return implClass;
158     }
159
160     /**
161      * <p>
162      * Returns the Java package to unmarshall classes to.
163      * </p>
164      *
165      * @return <code>String</code> - the Java package to unmarshall to.
166      */

167     public String JavaDoc getJavaPackage() {
168         if (javaPackage == null) {
169             javaPackage = "";
170         }
171         return javaPackage;
172     }
173
174     /**
175      * <p>
176      * Sets the Java package to unmarshall classes to.
177      * </p>
178      *
179      * @param javaPackage <code>String</code> the Java package to unmarshall
180      * to.
181      */

182     public void setJavaPackage(String JavaDoc javaPackage) {
183         this.javaPackage = javaPackage;
184     }
185
186     /**
187      * <p>
188      * This method is the public entry point for unmarshalling an object from
189      * an XML instance document. Note that the raw converted object is not
190      * directly returned, but is available through the
191      * <code>{@link UnmarshalledObject#getObject}</code> method on
192      * <code>{@link UnmarshalledObject}</code>.
193      * </p><p>
194      * XXX: We need to eventually put in Zeus exceptions here,
195      * not IOExceptions.
196      * </p>
197      *
198      * @param instanceURL <code>URL</code> for the instance document.
199      * @return <code>UnmarshalledObject</code> - the created Java object, or
200      * <code>null</code> if problems occur in a way that does not
201      * generate an <code>Exception</code>.
202      * @throws <code>IOException</code> when errors in binding occur.
203      */

204     public UnmarshalledObject unmarshal(Source sourceXML) throws IOException JavaDoc {
205         // Obtain the JDOM Document from the supplied source
206
Document doc = sourceXML.getDocument();
207         Element rootElement = doc.getRootElement();
208                
209         // Convert to Java
210
Object JavaDoc obj = getJavaRepresentation(rootElement);
211  
212         // Wrap in UnmarshalledObject
213
DocType docType = doc.getDocType();
214         if (docType != null) {
215             return new UnmarshalledObject(obj,
216                                           docType.getPublicID(),
217                                           docType.getSystemID());
218         } else {
219             return new UnmarshalledObject(obj);
220         }
221     }
222
223     /**
224      * <p>
225      * This will take an XML document fragment, starting with the provided
226      * <code>Element</code>, and convert it to a Java representation.
227      * </p>
228      *
229      * @param rootElement <code>Element</code> to begin processing on.
230      * @return <code>Object</code> - unmarshalled Java object.
231      * @throws <code>IOException</code> - when XML cannot be converted to Java.
232      */

233     private Object JavaDoc getJavaRepresentation(Element element)
234         throws IOException JavaDoc {
235
236         // If debugging, print out the element being processed
237
if (debug) {
238             System.out.println("Unmarshaller.getJavaRepresentation: " +
239                 element.getName());
240         }
241
242         Object JavaDoc obj = null;
243         String JavaDoc className = null;
244         try {
245             Namespace ns = element.getNamespace();
246
247             // Class name is root element name, with initial caps, plus "Impl"
248
className = getImplClass(NamingUtils.getJavaClassName(
249                 element.getName()));
250
251             Class JavaDoc objectClass;
252             
253             // Get the class
254
String JavaDoc pkg = getJavaPackage();
255             if (pkg.equals("")) {
256                 objectClass = getClass().getClassLoader().loadClass(className);
257             } else {
258                 objectClass =
259                     getClass().getClassLoader().loadClass(new StringBuffer JavaDoc()
260                         .append(pkg)
261                         .append(".")
262                         .append(className)
263                         .toString());
264             }
265
266             // Get a new instance of this class
267
obj = objectClass.newInstance();
268
269             // For each attribute, get its name and call mutator
270
List JavaDoc attributes = element.getAttributes();
271             Method JavaDoc[] methods = objectClass.getMethods();
272
273             for (Iterator JavaDoc i = attributes.iterator(); i.hasNext(); ) {
274                 Attribute att = (Attribute)i.next();
275                 
276                 // If debugging, print out the attribute being processed
277
if (debug) {
278                     System.out.println(
279                         " Processing attribute : " + att.getName());
280                 }
281
282                 // We only want attributes for this namespace
283
if ((!att.getNamespace().equals(ns)) &&
284                     (!att.getNamespace().equals(Namespace.NO_NAMESPACE))) {
285                     continue;
286                 }
287
288                 // Determine method to call - no Lists to worry about
289
// with attributes
290
String JavaDoc methodName = new StringBuffer JavaDoc()
291                     .append("set")
292                     .append(NamingUtils.getJavaClassName(att.getName()))
293                     .toString();
294
295                 // Find the method to call, and its parameter type
296
for (int j=0; j<methods.length; j++) {
297                     if (methods[j].getName().equals(methodName)) {
298                         // Since all mutators have one param, get the first one
299
Class JavaDoc[] paramTypes = methods[j].getParameterTypes();
300                         Class JavaDoc paramType = paramTypes[0];
301
302                         // Convert the type we have to the correct type
303
Object JavaDoc param =
304                             ClassUtils.getParameter(att.getValue(), paramType);
305
306                         // Invoke the method
307
methods[j].invoke(obj, new Object JavaDoc[] { param });
308                     }
309                 }
310             }
311
312             // Now do complex objects
313
List JavaDoc children = element.getChildren();
314
315             for (Iterator JavaDoc i = children.iterator(); i.hasNext(); ) {
316                 Element child = (Element)i.next();
317                 // If debugging, print out the element being processed
318
if (debug) {
319                     System.out.println(" Processing Child Element: " +
320                         child.getName());
321                 }
322
323                 // We only want elements for this namespace
324
if ((!child.getNamespace().equals(ns)) &&
325                     (!child.getNamespace().equals(Namespace.NO_NAMESPACE))) {
326                     continue;
327                 }
328
329                 // Determine method to call - start with default name
330
String JavaDoc singularMethodName = new StringBuffer JavaDoc()
331                     .append("set")
332                     .append(NamingUtils.getJavaClassName(child.getName()))
333                     .toString();
334                 String JavaDoc listMethodName = new StringBuffer JavaDoc()
335                     .append("add")
336                     .append(NamingUtils.getJavaClassName(child.getName()))
337                     .toString();
338
339                 // Find the method to call, and its parameter type
340
for (int j=0; j<methods.length; j++) {
341                     if (methods[j].getName().equals(singularMethodName)) {
342                         // Since all mutators have one param, get the first one
343
Class JavaDoc[] paramTypes = methods[j].getParameterTypes();
344                         Class JavaDoc paramType = paramTypes[0];
345
346                         // Convert the type we have to the correct type
347
Object JavaDoc param = null;
348
349                         if (isSimpleElement(child)) {
350                             param = child.getTextTrim();
351                         } else {
352                             param = getJavaRepresentation(child);
353                         }
354
355                         // Invoke the method:
356
// In case the arguments are wrong, the exception
357
// is caught and a recursive call is made
358
try {
359                             methods[j].invoke(obj, new Object JavaDoc[] { param });
360                         } catch (IllegalArgumentException JavaDoc ilex) {
361                             param = getJavaRepresentation(child);
362                             methods[j].invoke(obj, new Object JavaDoc[] { param });
363                         }
364
365                         break;
366                     } else if (methods[j].getName().equals(listMethodName)) {
367                         Class JavaDoc[] paramTypes = methods[j].getParameterTypes();
368                         Class JavaDoc paramType = paramTypes[0];
369                         
370                         // Convert the type we have to the correct type
371
Object JavaDoc param = null;
372                         if (isSimpleElement(child)) {
373                             param = child.getTextTrim();
374                         } else {
375                             param = getJavaRepresentation(child);
376                         }
377                         
378                         // Invoke the method:
379
// In case the arguments are wrong, the exception
380
// is caught and a recursive call is made
381
try {
382                             methods[j].invoke(obj, new Object JavaDoc[] { param });
383                         } catch (IllegalArgumentException JavaDoc ilex) {
384                             param = getJavaRepresentation(child);
385                         }
386
387                         break;
388                     }
389                 }
390             }
391             
392             // Finally, deal with element content
393
String JavaDoc content = element.getTextTrim();
394             if ((content != null) && (content.length() > 0)) {
395                 String JavaDoc methodName = "setValue";
396                 Method JavaDoc method
397                     = objectClass.getMethod(methodName,
398                                             new Class JavaDoc[] { String JavaDoc.class });
399                 method.invoke(obj, new Object JavaDoc[] { content });
400             }
401             
402         } catch (ClassNotFoundException JavaDoc e) {
403             throw new IOException JavaDoc("The class that the XML instance document " +
404                                   "should be unmarshalled as an instance of, " +
405                                   className +
406                                   ", must be generated and on the classpath. " +
407                                   "The current classpath is '" +
408                                   System.getProperty("java.class.path") + "'.");
409         } catch (InstantiationException JavaDoc e) {
410             throw new IOException JavaDoc("Could not create a new instance of the " +
411                                   "class " + className);
412         } catch (IllegalAccessException JavaDoc e) {
413             throw new IOException JavaDoc("Could not access the class " + className);
414         } catch (IllegalArgumentException JavaDoc e) {
415             throw new IOException JavaDoc("Could not pass arguments to mutator " +
416                                   "method on new instance.");
417         } catch (InvocationTargetException JavaDoc e) {
418             throw new IOException JavaDoc("Error in accessing mutator on new " +
419                                   "instance of class.");
420         } catch (NoSuchMethodException JavaDoc e) {
421             throw new IOException JavaDoc(
422                 "Error in setting data method on new instance of class.");
423         }
424         return obj;
425     }
426
427     /**
428      * <p>
429      * This will determine if an element is "simple", having neither
430      * attributes nor child elements.
431      * </p>
432      *
433      * @param element <code>Element</code> to inspect
434      * @return <code>boolean</code> - whether the element is simple.
435      */

436     private boolean isSimpleElement(Element element) {
437         boolean simple = true;
438         if (element.getChildren().isEmpty()) {
439             List JavaDoc attributes = element.getAttributes();
440             int size = attributes.size();
441             Attribute attr = null;
442             String JavaDoc typeStr = "dt_";
443             
444             // FIXME: If there is an implied attribute, then the element may
445
// look like a simple element even though it is not.
446
for (int i = 0; i < size; i++) {
447                 attr = (Attribute)attributes.get(i);
448                 if (!attr.getName().startsWith(typeStr)) {
449                     simple = false;
450                     break;
451                 }
452             }
453         } else {
454             simple = false;
455         }
456         return simple;
457     }
458 }
459
Popular Tags