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