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