Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/apache/axis/encoding/ser/BeanDeserializer.java


1   /*
2    * Copyright 2001-2002,2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.axis.encoding.ser;
18  
19  import org.apache.axis.Constants;
20  import org.apache.axis.components.logger.LogFactory;
21  import org.apache.axis.description.ElementDesc;
22  import org.apache.axis.description.FieldDesc;
23  import org.apache.axis.description.TypeDesc;
24  import org.apache.axis.encoding.ConstructorTarget;
25  import org.apache.axis.encoding.DeserializationContext;
26  import org.apache.axis.encoding.Deserializer;
27  import org.apache.axis.encoding.DeserializerImpl;
28  import org.apache.axis.encoding.Target;
29  import org.apache.axis.encoding.TypeMapping;
30  import org.apache.axis.message.MessageElement;
31  import org.apache.axis.message.SOAPHandler;
32  import org.apache.axis.soap.SOAPConstants;
33  import org.apache.axis.utils.BeanPropertyDescriptor;
34  import org.apache.axis.utils.Messages;
35  import org.apache.commons.logging.Log;
36  import org.xml.sax.Attributes;
37  import org.xml.sax.SAXException;
38  
39  import javax.xml.namespace.QName;
40  import java.io.CharArrayWriter;
41  import java.io.Serializable;
42  import java.lang.reflect.Constructor;
43  import java.util.Map;
44  
45  /**
46   * General purpose deserializer for an arbitrary java bean.
47   *
48   * @author Sam Ruby <rubys@us.ibm.com>
49   * @author Rich Scheuerle <scheu@us.ibm.com>
50   * @author Tom Jordahl <tomj@macromedia.com>
51   */
52  public class BeanDeserializer extends DeserializerImpl implements Serializable
53  {
54      protected static Log log =
55          LogFactory.getLog(BeanDeserializer.class.getName());
56  
57      private final CharArrayWriter val = new CharArrayWriter();
58  
59      QName xmlType;
60      Class javaType;
61      protected Map propertyMap = null;
62      protected QName prevQName;
63  
64      /**
65       *  Constructor if no default constructor
66       */
67      protected Constructor constructorToUse = null;
68  
69      /**
70       * Constructor Target object to use (if constructorToUse != null)
71       */
72      protected Target constructorTarget = null;
73      
74      /** Type metadata about this class for XML deserialization */
75      protected TypeDesc typeDesc = null;
76  
77      // This counter is updated to deal with deserialize collection properties
78      protected int collectionIndex = -1;
79  
80      protected SimpleDeserializer cacheStringDSer = null;
81      protected QName cacheXMLType = null;
82  
83      // Construct BeanSerializer for the indicated class/qname
84      public BeanDeserializer(Class javaType, QName xmlType) {
85          this(javaType, xmlType, TypeDesc.getTypeDescForClass(javaType));
86      }
87  
88      // Construct BeanDeserializer for the indicated class/qname and meta Data
89      public BeanDeserializer(Class javaType, QName xmlType, TypeDesc typeDesc ) {
90          this(javaType, xmlType, typeDesc,
91               BeanDeserializerFactory.getProperties(javaType, typeDesc));
92      }
93  
94      // Construct BeanDeserializer for the indicated class/qname and meta Data
95      public BeanDeserializer(Class javaType, QName xmlType, TypeDesc typeDesc,
96                              Map propertyMap ) {
97          this.xmlType = xmlType;
98          this.javaType = javaType;
99          this.typeDesc = typeDesc;
100         this.propertyMap = propertyMap;
101 
102         // create a value
103         try {
104             value=javaType.newInstance();
105         } catch (Exception e) {
106             // Don't process the exception at this point.
107             // This is defered until the call to startElement
108             // which will throw the exception.
109         }
110     }
111 
112     /**
113      * startElement
114      * 
115      * The ONLY reason that this method is overridden is so that
116      * the object value can be set or a reasonable exception is thrown
117      * indicating that the object cannot be created.  This is done
118      * at this point so that it occurs BEFORE href/id processing.
119      * @param namespace is the namespace of the element
120      * @param localName is the name of the element
121      * @param prefix is the prefix of the element
122      * @param attributes are the attributes on the element...used to get the
123      *                   type
124      * @param context is the DeserializationContext
125      */
126     public void startElement(String namespace, String localName,
127                              String prefix, Attributes attributes,
128                              DeserializationContext context)
129         throws SAXException
130     {
131         // Create the bean object if it was not already
132         // created in the constructor.
133         if (value == null) {
134             try {
135                 value=javaType.newInstance();
136             } catch (Exception e) {
137                 // Use first found constructor.
138                 // Note : the right way is to use XML mapping information
139                 // for example JSR 109's constructor-parameter-order
140                 Constructor[] constructors = javaType.getConstructors();
141                 if (constructors.length > 0) {
142                     constructorToUse = constructors[0];
143                 }
144 
145                 // Failed to create an object if no constructor
146                 if (constructorToUse == null) {
147                     throw new SAXException(Messages.getMessage("cantCreateBean00", 
148                                                             javaType.getName(), 
149                                                             e.toString()));
150                 }
151             }
152         }
153         // Invoke super.startElement to do the href/id processing.
154         super.startElement(namespace, localName, 
155                            prefix, attributes, context);
156     }
157 
158     /**
159      * Deserializer interface called on each child element encountered in
160      * the XML stream.
161      * @param namespace is the namespace of the child element
162      * @param localName is the local name of the child element
163      * @param prefix is the prefix used on the name of the child element
164      * @param attributes are the attributes of the child element
165      * @param context is the deserialization context.
166      * @return is a Deserializer to use to deserialize a child (must be
167      * a derived class of SOAPHandler) or null if no deserialization should
168      * be performed.
169      */
170     public SOAPHandler onStartChild(String namespace,
171                                     String localName,
172                                     String prefix,
173                                     Attributes attributes,
174                                     DeserializationContext context)
175         throws SAXException
176     {
177         handleMixedContent();
178 
179         BeanPropertyDescriptor propDesc = null;
180         FieldDesc fieldDesc = null;
181 
182         SOAPConstants soapConstants = context.getSOAPConstants();
183         String encodingStyle = context.getEncodingStyle();
184         boolean isEncoded = Constants.isSOAP_ENC(encodingStyle);
185 
186         QName elemQName = new QName(namespace, localName);
187         // The collectionIndex needs to be reset for Beans with multiple arrays
188         if ((prevQName == null) || (!prevQName.equals(elemQName))) {
189             collectionIndex = -1;
190         }  
191 
192         boolean isArray = false;
193         QName itemQName = null;
194         if (typeDesc != null) {
195             // Lookup the name appropriately (assuming an unqualified
196             // name for SOAP encoding, using the namespace otherwise)
197             String fieldName = typeDesc.getFieldNameForElement(elemQName, 
198                                                                isEncoded);
199             propDesc = (BeanPropertyDescriptor)propertyMap.get(fieldName);
200             fieldDesc = typeDesc.getFieldByName(fieldName);
201 
202             if (fieldDesc != null) {
203                ElementDesc element = (ElementDesc)fieldDesc;
204                isArray = element.isMaxOccursUnbounded();
205                itemQName = element.getItemQName();
206            }
207         }
208 
209         if (propDesc == null) {
210             // look for a field by this name.
211             propDesc = (BeanPropertyDescriptor) propertyMap.get(localName);
212         }
213 
214         // try and see if this is an xsd:any namespace="##any" element before
215         // reporting a problem
216         if (propDesc == null || 
217             (((prevQName != null) && prevQName.equals(elemQName) &&
218                 !(propDesc.isIndexed()||isArray)
219             && getAnyPropertyDesc() != null ))) {
220             // try to put unknown elements into a SOAPElement property, if
221             // appropriate
222           prevQName = elemQName;
223             propDesc = getAnyPropertyDesc();
224             if (propDesc != null) {
225                 try {
226                     MessageElement [] curElements = (MessageElement[])propDesc.get(value);
227                     int length = 0;
228                     if (curElements != null) {
229                         length = curElements.length;
230                     }
231                     MessageElement [] newElements = new MessageElement[length + 1];
232                     if (curElements != null) {
233                         System.arraycopy(curElements, 0,
234                                          newElements, 0, length);
235                     }
236                     MessageElement thisEl = context.getCurElement();
237 
238                     newElements[length] = thisEl;
239                     propDesc.set(value, newElements);
240                     // if this is the first pass through the MessageContexts
241                     // make sure that the correct any element is set,
242                     // that is the child of the current MessageElement, however
243                     // on the first pass this child has not been set yet, so
244                     // defer it to the child SOAPHandler
245                     if (!localName.equals(thisEl.getName())) {
246                         return new SOAPHandler(newElements, length);
247                     }
248                     return new SOAPHandler();
249                 } catch (Exception e) {
250                     throw new SAXException(e);
251                 }
252             }
253         }
254 
255 
256         if (propDesc == null) {
257             // No such field
258             throw new SAXException(
259                     Messages.getMessage("badElem00", javaType.getName(), 
260                                          localName));
261         }
262 
263         prevQName = elemQName;
264         // Get the child's xsi:type if available
265         QName childXMLType = context.getTypeFromAttributes(namespace, 
266                                                             localName,
267                                                             attributes);
268 
269         String href = attributes.getValue(soapConstants.getAttrHref());
270         Class fieldType = propDesc.getType();
271 
272         // If no xsi:type or href, check the meta-data for the field
273         if (childXMLType == null && fieldDesc != null && href == null) {
274             childXMLType = fieldDesc.getXmlType();
275             if (itemQName != null) {
276                 // This is actually a wrapped literal array and should be
277                 // deserialized with the ArrayDeserializer
278                 childXMLType = Constants.SOAP_ARRAY;
279                 fieldType = propDesc.getActualType();
280             } else {
281                 childXMLType = fieldDesc.getXmlType();
282             }
283         }
284         
285         // Get Deserializer for child, default to using DeserializerImpl
286         Deserializer dSer = getDeserializer(childXMLType,
287                                             fieldType,
288                                             href,
289                                             context);
290 
291         // It is an error if the dSer is not found - the only case where we
292         // wouldn't have a deserializer at this point is when we're trying
293         // to deserialize something we have no clue about (no good xsi:type,
294         // no good metadata).
295         if (dSer == null) {
296             dSer = context.getDeserializerForClass(propDesc.getType());
297         }
298 
299         // Fastpath nil checks...
300         if (context.isNil(attributes)) {
301             if (propDesc != null && (propDesc.isIndexed()||isArray)) {
302                 if (!((dSer != null) && (dSer instanceof ArrayDeserializer))) {
303                     collectionIndex++;
304                     dSer.registerValueTarget(new BeanPropertyTarget(value,
305                             propDesc, collectionIndex));
306                     addChildDeserializer(dSer);
307                     return (SOAPHandler)dSer;
308                 }
309             }
310             return null;
311         }            
312           
313         if (dSer == null) {
314             throw new SAXException(Messages.getMessage("noDeser00",
315                                                        childXMLType.toString()));
316         }
317 
318         if (constructorToUse != null) {
319             if (constructorTarget == null) {
320                 constructorTarget = new ConstructorTarget(constructorToUse, this);
321             }
322             dSer.registerValueTarget(constructorTarget);
323         } else if (propDesc.isWriteable()) {
324             // If this is an indexed property, and the deserializer we found
325             // was NOT the ArrayDeserializer, this is a non-SOAP array:
326             // <bean>
327             //   <field>value1</field>
328             //   <field>value2</field>
329             // ...
330             // In this case, we want to use the collectionIndex and make sure
331             // the deserialized value for the child element goes into the
332             // right place in the collection.
333 
334             // Register value target
335             if ((itemQName != null || propDesc.isIndexed() || isArray) && !(dSer instanceof ArrayDeserializer)) {
336                 collectionIndex++;
337                 dSer.registerValueTarget(new BeanPropertyTarget(value,
338                         propDesc, collectionIndex));
339             } else {
340                 // If we're here, the element maps to a single field value,
341                 // whether that be a "basic" type or an array, so use the
342                 // normal (non-indexed) BeanPropertyTarget form.
343                 collectionIndex = -1;
344                 dSer.registerValueTarget(new BeanPropertyTarget(value,
345                                                                 propDesc));
346             }
347         }
348         
349         // Let the framework know that we need this deserializer to complete
350         // for the bean to complete.
351         addChildDeserializer(dSer);
352         
353         return (SOAPHandler)dSer;
354     }
355 
356     /**
357      * Get a BeanPropertyDescriptor which indicates where we should
358      * put extensibility elements (i.e. XML which falls under the
359      * auspices of an &lt;xsd:any&gt; declaration in the schema)
360      *
361      * @return an appropriate BeanPropertyDescriptor, or null
362      */
363     public BeanPropertyDescriptor getAnyPropertyDesc() {
364         if (typeDesc == null)
365             return null;
366         
367        return typeDesc.getAnyDesc();
368     }
369 
370     /**
371      * Set the bean properties that correspond to element attributes.
372      * 
373      * This method is invoked after startElement when the element requires
374      * deserialization (i.e. the element is not an href and the value is not
375      * nil.)
376      * @param namespace is the namespace of the element
377      * @param localName is the name of the element
378      * @param prefix is the prefix of the element
379      * @param attributes are the attributes on the element...used to get the
380      *                   type
381      * @param context is the DeserializationContext
382      */
383     public void onStartElement(String namespace, String localName,
384                                String prefix, Attributes attributes,
385                                DeserializationContext context)
386             throws SAXException {
387 
388         // The value should have been created or assigned already.
389         // This code may no longer be needed.
390         if (value == null && constructorToUse == null) {
391             // create a value
392             try {
393                 value=javaType.newInstance();
394             } catch (Exception e) {
395                 throw new SAXException(Messages.getMessage("cantCreateBean00", 
396                                                             javaType.getName(), 
397                                                             e.toString()));
398             }
399         }
400 
401         // If no type description meta data, there are no attributes,
402         // so we are done.
403         if (typeDesc == null)
404             return;
405 
406         // loop through the attributes and set bean properties that 
407         // correspond to attributes
408         for (int i=0; i < attributes.getLength(); i++) {
409             QName attrQName = new QName(attributes.getURI(i),
410                                         attributes.getLocalName(i));
411             String fieldName = typeDesc.getFieldNameForAttribute(attrQName);
412             if (fieldName == null)
413                 continue;
414 
415             FieldDesc fieldDesc = typeDesc.getFieldByName(fieldName);
416             
417             // look for the attribute property
418             BeanPropertyDescriptor bpd =
419                     (BeanPropertyDescriptor) propertyMap.get(fieldName);
420             if (bpd != null) {
421                 if (constructorToUse == null) {
422                     // check only if default constructor
423                     if (!bpd.isWriteable() || bpd.isIndexed()) continue ;
424                 }
425                 
426                 // Get the Deserializer for the attribute
427                 Deserializer dSer = getDeserializer(fieldDesc.getXmlType(),
428                                                     bpd.getType(), 
429                                                     null,
430                                                     context);
431                 if (dSer == null) {
432                     dSer = context.getDeserializerForClass(bpd.getType());
433                 }
434                 if (dSer == null)
435                     throw new SAXException(
436                             Messages.getMessage("unregistered00",
437                                                  bpd.getType().toString()));
438                 
439                 if (! (dSer instanceof SimpleDeserializer))
440                     throw new SAXException(
441                             Messages.getMessage("AttrNotSimpleType00", 
442                                                  bpd.getName(), 
443                                                  bpd.getType().toString()));
444                 
445                 // Success!  Create an object from the string and set
446                 // it in the bean
447                 try {
448                     dSer.onStartElement(namespace, localName, prefix,
449                                         attributes, context);
450                     Object val = ((SimpleDeserializer)dSer).
451                         makeValue(attributes.getValue(i));
452                     if (constructorToUse == null) {
453                         bpd.set(value, val);
454                     } else {
455                         // add value for our constructor
456                         if (constructorTarget == null) {
457                             constructorTarget = new ConstructorTarget(constructorToUse, this);
458                         }
459                         constructorTarget.set(val);
460                     }
461                 } catch (Exception e) {
462                     throw new SAXException(e);
463                 }
464                 
465             } // if
466         } // attribute loop
467     }
468 
469     /**
470      * Get the Deserializer for the attribute or child element.
471      * @param xmlType QName of the attribute/child element or null if not known.
472      * @param javaType Class of the corresponding property
473      * @param href String is the value of the href attribute, which is used
474      *             to determine whether the child element is complete or an 
475      *             href to another element.
476      * @param context DeserializationContext
477      * @return Deserializer or null if not found.
478     */
479     protected Deserializer getDeserializer(QName xmlType, 
480                                            Class javaType, 
481                                            String href,
482                                            DeserializationContext context) {
483         if (javaType.isArray()) {
484             context.setDestinationClass(javaType);
485         } 
486         // See if we have a cached deserializer
487         if (cacheStringDSer != null) {
488             if (String.class.equals(javaType) &&
489                 href == null &&
490                 (cacheXMLType == null && xmlType == null ||
491                  cacheXMLType != null && cacheXMLType.equals(xmlType))) {
492                 cacheStringDSer.reset();
493                 return cacheStringDSer;
494             }
495         }
496         
497         Deserializer dSer = null;
498 
499         if (xmlType != null && href == null) {
500             // Use the xmlType to get the deserializer.
501             dSer = context.getDeserializerForType(xmlType);
502         } else {
503             // If the xmlType is not set, get a default xmlType
504             TypeMapping tm = context.getTypeMapping();
505             QName defaultXMLType = tm.getTypeQName(javaType);
506             // If there is not href, then get the deserializer
507             // using the javaType and default XMLType,
508             // If there is an href, the create the generic
509             // DeserializerImpl and set its default type (the
510             // default type is used if the href'd element does 
511             // not have an xsi:type.
512             if (href == null) {
513                 dSer = context.getDeserializer(javaType, defaultXMLType);
514             } else {
515                 dSer = new DeserializerImpl();
516                 context.setDestinationClass(javaType);
517                 dSer.setDefaultType(defaultXMLType);
518             }
519         }
520         if (javaType.equals(String.class) &&
521             dSer instanceof SimpleDeserializer) {
522             cacheStringDSer = (SimpleDeserializer) dSer;
523             cacheXMLType = xmlType;
524         }
525         return dSer;
526     }
527 
528     public void characters(char[] chars, int start, int end) throws SAXException {
529         val.write(chars, start, end);
530     }
531 
532     public void onEndElement(String namespace, String localName,
533                              DeserializationContext context) throws SAXException {
534         handleMixedContent();
535     }
536 
537     protected void handleMixedContent() throws SAXException {
538         BeanPropertyDescriptor propDesc = getAnyPropertyDesc();
539         if (propDesc == null || val.size() == 0) {
540             return;
541         }
542         String textValue = val.toString().trim();
543         val.reset();
544         if (textValue.length() == 0) {
545             return;
546         }
547         try {
548             MessageElement[] curElements = (MessageElement[]) propDesc.get(value);
549             int length = 0;
550             if (curElements != null) {
551                 length = curElements.length;
552             }
553             MessageElement[] newElements = new MessageElement[length + 1];
554             if (curElements != null) {
555                 System.arraycopy(curElements, 0,
556                         newElements, 0, length);
557             }
558             MessageElement thisEl = new MessageElement(new org.apache.axis.message.Text(textValue));
559             newElements[length] = thisEl;
560             propDesc.set(value, newElements);
561         } catch (Exception e) {
562             throw new SAXException(e);
563         }
564     }
565 }