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/BeanSerializer.java


1   /*
2    * Copyright 2001-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.AxisFault;
20  import org.apache.axis.Constants;
21  import org.apache.axis.components.logger.LogFactory;
22  import org.apache.axis.description.FieldDesc;
23  import org.apache.axis.description.TypeDesc;
24  import org.apache.axis.description.ElementDesc;
25  import org.apache.axis.encoding.SerializationContext;
26  import org.apache.axis.encoding.Serializer;
27  import org.apache.axis.message.MessageElement;
28  import org.apache.axis.utils.BeanPropertyDescriptor;
29  import org.apache.axis.utils.BeanUtils;
30  import org.apache.axis.utils.JavaUtils;
31  import org.apache.axis.utils.Messages;
32  import org.apache.axis.utils.FieldPropertyDescriptor;
33  import org.apache.axis.wsdl.fromJava.Types;
34  import org.apache.axis.wsdl.symbolTable.SchemaUtils;
35  import org.apache.commons.logging.Log;
36  import org.w3c.dom.Element;
37  import org.xml.sax.Attributes;
38  import org.xml.sax.helpers.AttributesImpl;
39  
40  import javax.xml.namespace.QName;
41  import java.io.IOException;
42  import java.io.Serializable;
43  import java.lang.reflect.InvocationTargetException;
44  import java.lang.reflect.Modifier;
45  import java.lang.reflect.Constructor;
46  import java.util.List;
47  
48  /**
49   * General purpose serializer/deserializerFactory for an arbitrary java bean.
50   *
51   * @author Sam Ruby <rubys@us.ibm.com>
52   * @author Rich Scheuerle <scheu@us.ibm.com>
53   * @author Tom Jordahl <tomj@macromedia.com>
54   */
55  public class BeanSerializer implements Serializer, Serializable {
56  
57      protected static Log log =
58          LogFactory.getLog(BeanSerializer.class.getName());
59  
60      private static final QName MUST_UNDERSTAND_QNAME = 
61          new QName(Constants.URI_SOAP11_ENV, Constants.ATTR_MUST_UNDERSTAND);
62      private static final Object[] ZERO_ARGS =
63          new Object [] { "0" };
64  
65      QName xmlType;
66      Class javaType;
67  
68      protected BeanPropertyDescriptor[] propertyDescriptor = null;
69      protected TypeDesc typeDesc = null;
70  
71      // Construct BeanSerializer for the indicated class/qname
72      public BeanSerializer(Class javaType, QName xmlType) {
73          this(javaType, xmlType, TypeDesc.getTypeDescForClass(javaType));
74      }
75  
76      // Construct BeanSerializer for the indicated class/qname
77      public BeanSerializer(Class javaType, QName xmlType, TypeDesc typeDesc) {
78          this(javaType, xmlType, typeDesc, null);
79  
80          if (typeDesc != null) {
81              propertyDescriptor = typeDesc.getPropertyDescriptors();
82          } else {
83              propertyDescriptor = BeanUtils.getPd(javaType, null);
84          }
85      }
86  
87      // Construct BeanSerializer for the indicated class/qname/propertyDesc
88      public BeanSerializer(Class javaType, QName xmlType, TypeDesc typeDesc,
89                            BeanPropertyDescriptor[] propertyDescriptor) {
90          this.xmlType = xmlType;
91          this.javaType = javaType;
92          this.typeDesc = typeDesc;
93          this.propertyDescriptor = propertyDescriptor;
94      }
95  
96      /**
97       * Serialize a bean.  Done simply by serializing each bean property.
98       * @param name is the element name
99       * @param attributes are the attributes...serialize is free to add more.
100      * @param value is the value
101      * @param context is the SerializationContext
102      */
103     public void serialize(QName name, Attributes attributes,
104                           Object value, SerializationContext context)
105         throws IOException
106     {
107         // Check for meta-data in the bean that will tell us if any of the
108         // properties are actually attributes, add those to the element
109         // attribute list
110         Attributes beanAttrs = getObjectAttributes(value, attributes, context);
111 
112         // Get the encoding style
113         boolean isEncoded = context.isEncoded();
114 
115         // check whether we have and xsd:any namespace="##any" type
116         boolean suppressElement = !isEncoded &&
117                                   name.getNamespaceURI().equals("") &&
118                                   name.getLocalPart().equals("any");
119 
120         if (!suppressElement)
121             context.startElement(name, beanAttrs);
122 
123         // check whether the array is converted to ArrayOfT shema type    
124         if (value.getClass().isArray()) {
125            Object newVal = JavaUtils.convert(value, javaType); 
126            if (newVal != null && javaType.isAssignableFrom(newVal.getClass())) {
127                value = newVal; 
128            }
129         }
130         try {
131             // Serialize each property
132             for (int i=0; i<propertyDescriptor.length; i++) {
133                 String propName = propertyDescriptor[i].getName();
134                 if (propName.equals("class"))
135                     continue;
136                 QName qname = null;
137                 QName xmlType = null;
138                 Class javaType = propertyDescriptor[i].getType();
139 
140                 boolean isOmittable = false;
141                 // isNillable default value depends on the field type
142                 boolean isNillable = Types.isNullable(javaType);
143                 // isArray
144                 boolean isArray = false;
145                 QName itemQName = null;
146 
147                 // If we have type metadata, check to see what we're doing
148                 // with this field.  If it's an attribute, skip it.  If it's
149                 // an element, use whatever qname is in there.  If we can't
150                 // find any of this info, use the default.
151                 if (typeDesc != null) {
152                     FieldDesc field = typeDesc.getFieldByName(propName);
153                     if (field != null) {
154                         if (!field.isElement()) {
155                             continue;
156                         }
157 
158                         ElementDesc element = (ElementDesc)field;
159 
160                         // If we're SOAP encoded, just use the local part,
161                         // not the namespace.  Otherwise use the whole
162                         // QName.
163                         if (isEncoded) {
164                             qname = new QName(element.getXmlName().getLocalPart());
165                         } else {
166                             qname = element.getXmlName();
167                         }
168                         isOmittable = element.isMinOccursZero();
169                         isNillable = element.isNillable();
170                         isArray = element.isMaxOccursUnbounded();
171                         xmlType = element.getXmlType();
172                         itemQName = element.getItemQName();
173                         context.setItemQName(itemQName);
174                     }
175                 }
176 
177                 if (qname == null) {
178                     qname = new QName(isEncoded ? "" : name.getNamespaceURI(),
179                                       propName);
180                 }
181 
182                 if (xmlType == null) {
183                     // look up the type QName using the class
184                     xmlType = context.getQNameForClass(javaType);
185                 }
186 
187                 // Read the value from the property
188                 if (propertyDescriptor[i].isReadable()) {
189                     if (itemQName != null ||
190                             (!propertyDescriptor[i].isIndexed() && !isArray)) {
191                         // Normal case: serialize the value
192                         Object propValue =
193                             propertyDescriptor[i].get(value);
194 
195 
196                         if (propValue == null) {
197                             // an element cannot be null if nillable property is set to
198                             // "false" and the element cannot be omitted
199                             if (!isNillable && !isOmittable) {
200                                 if (Number.class.isAssignableFrom(javaType)) {
201                                     // If we have a null and it's a number, though,
202                                     // we might turn it into the appropriate kind of 0.
203                                     // TODO : Should be caching these constructors?
204                                     try {
205                                         Constructor constructor =
206                                                 javaType.getConstructor(
207                                                         SimpleDeserializer.STRING_CLASS);
208                                         propValue = constructor.newInstance(ZERO_ARGS);
209                                     } catch (Exception e) {
210                                         // If anything goes wrong here, oh well we tried.
211                                     }
212                                 }
213 
214                                 if (propValue == null) {
215                                     throw new IOException(
216                                             Messages.getMessage(
217                                                     "nullNonNillableElement",
218                                                     propName));
219                                 }
220                             }
221 
222                             // if meta data says minOccurs=0, then we can skip
223                             // it if its value is null and we aren't doing SOAP
224                             // encoding.
225                             if (isOmittable && !isEncoded) {
226                                 continue;
227                             }
228                         }
229 
230                         context.serialize(qname,
231                                           null,
232                                           propValue,
233                                           xmlType);
234                     } else {
235                         // Collection of properties: serialize each one
236                         int j=0;
237                         while(j >= 0) {
238                             Object propValue = null;
239                             try {
240                                 propValue =
241                                     propertyDescriptor[i].get(value, j);
242                                 j++;
243                             } catch (Exception e) {
244                                 j = -1;
245                             }
246                             if (j >= 0) {
247                                 context.serialize(qname, null,
248                                                   propValue, xmlType);
249                             }
250                         }
251                     }
252                 }
253             }
254 
255             BeanPropertyDescriptor anyDesc = typeDesc == null ? null :
256                     typeDesc.getAnyDesc();
257             if (anyDesc != null) {
258                 // If we have "extra" content here, it'll be an array
259                 // of MessageElements.  Serialize each one.
260                 Object anyVal = anyDesc.get(value);
261                 if (anyVal != null && anyVal instanceof MessageElement[]) {
262                     MessageElement [] anyContent = (MessageElement[])anyVal;
263                     for (int i = 0; i < anyContent.length; i++) {
264                         MessageElement element = anyContent[i];
265                         element.output(context);
266                     }
267                 }
268             }
269         } catch (InvocationTargetException ite) {
270             Throwable target = ite.getTargetException();
271             log.error(Messages.getMessage("exception00"), target);
272             throw new IOException(target.toString());
273         } catch (Exception e) {
274             log.error(Messages.getMessage("exception00"), e);
275             throw new IOException(e.toString());
276         }
277 
278         if (!suppressElement)
279             context.endElement();
280     }
281 
282 
283 
284     public String getMechanismType() { return Constants.AXIS_SAX; }
285 
286     /**
287      * Return XML schema for the specified type, suitable for insertion into
288      * the &lt;types&gt; element of a WSDL document, or underneath an
289      * &lt;element&gt; or &lt;attribute&gt; declaration.
290      *
291      * @param javaType the Java Class we're writing out schema for
292      * @param types the Java2WSDL Types object which holds the context
293      *              for the WSDL being generated.
294      * @return a type element containing a schema simpleType/complexType
295      * @see org.apache.axis.wsdl.fromJava.Types
296      */
297     public Element writeSchema(Class javaType, Types types) throws Exception {
298 
299         // ComplexType representation of bean class
300         Element complexType = types.createElement("complexType");
301 
302         // See if there is a super class, stop if we hit a stop class
303         Element e = null;
304         Class superClass = javaType.getSuperclass();
305         BeanPropertyDescriptor[] superPd = null;
306         List stopClasses = types.getStopClasses();
307         if (superClass != null &&
308                 superClass != java.lang.Object.class &&
309                 superClass != java.lang.Exception.class &&
310                 superClass != java.lang.Throwable.class &&
311                 superClass != java.lang.RuntimeException.class &&
312                 superClass != java.rmi.RemoteException.class &&
313                 superClass != org.apache.axis.AxisFault.class &&
314                 (stopClasses == null ||
315                 !(stopClasses.contains(superClass.getName()))) ) {
316             // Write out the super class
317             String base = types.writeType(superClass);
318             Element complexContent = types.createElement("complexContent");
319             complexType.appendChild(complexContent);
320             Element extension = types.createElement("extension");
321             complexContent.appendChild(extension);
322             extension.setAttribute("base", base);
323             e = extension;
324             // Get the property descriptors for the super class
325             TypeDesc superTypeDesc = TypeDesc.getTypeDescForClass(superClass);
326             if (superTypeDesc != null) {
327                 superPd = superTypeDesc.getPropertyDescriptors();
328             } else {
329                 superPd = BeanUtils.getPd(superClass, null);
330             }
331         } else {
332             e = complexType;
333         }
334 
335         // Add fields under sequence element.
336         // Note: In most situations it would be okay
337         // to put the fields under an all element.
338         // However it is illegal schema to put an
339         // element with minOccurs=0 or maxOccurs>1 underneath
340         // an all element.  This is the reason why a sequence
341         // element is used.
342         Element all = types.createElement("sequence");
343         e.appendChild(all);
344 
345         if (Modifier.isAbstract(javaType.getModifiers())) {
346             complexType.setAttribute("abstract", "true");
347         }
348 
349         // Serialize each property
350         for (int i=0; i<propertyDescriptor.length; i++) {
351             String propName = propertyDescriptor[i].getName();
352 
353             // Don't serializer properties named class
354             boolean writeProperty = true;
355             if (propName.equals("class")) {
356                 writeProperty = false;
357             }
358 
359             // Don't serialize the property if it is present
360             // in the super class property list
361             if (superPd != null && writeProperty) {
362                 for (int j=0; j<superPd.length && writeProperty; j++) {
363                     if (propName.equals(superPd[j].getName())) {
364                         writeProperty = false;
365                     }
366                 }
367             }
368             if (!writeProperty) {
369                 continue;
370             }
371 
372             // If we have type metadata, check to see what we're doing
373             // with this field.  If it's an attribute, skip it.  If it's
374             // an element, use whatever qname is in there.  If we can't
375             // find any of this info, use the default.
376 
377             if (typeDesc != null) {
378                 Class fieldType = propertyDescriptor[i].getType();
379                 FieldDesc field = typeDesc.getFieldByName(propName);
380 
381                 if (field != null) {
382                     QName qname = field.getXmlName();
383                     QName fieldXmlType = field.getXmlType();
384                     boolean isAnonymous = fieldXmlType != null && fieldXmlType.getLocalPart().startsWith(">");
385 
386                     if (qname != null) {
387                         // FIXME!
388                         // Check to see if this is in the right namespace -
389                         // if it's not, we need to use an <element ref="">
390                         // to represent it!!!
391 
392                         // Use the default...
393                         propName = qname.getLocalPart();
394                     }
395                     if (!field.isElement()) {
396                         writeAttribute(types,
397                                        propName,
398                                        fieldType,
399                                        fieldXmlType,
400                                        complexType);
401                     } else {
402                         writeField(types,
403                                    propName,
404                                    fieldXmlType,
405                                    fieldType,
406                                    propertyDescriptor[i].isIndexed(),
407                                    field.isMinOccursZero(),
408                                    all, isAnonymous,
409                                    ((ElementDesc)field).getItemQName());
410                     }
411                 } else {
412                     writeField(types,
413                                propName,
414                                null,
415                                fieldType,
416                                propertyDescriptor[i].isIndexed(), false, all, false, null);
417                 }
418             } else {
419                 boolean done = false;
420                 if (propertyDescriptor[i] instanceof FieldPropertyDescriptor){
421                     FieldPropertyDescriptor fpd = (FieldPropertyDescriptor) propertyDescriptor[i];
422                     Class clazz = fpd.getField().getType();
423                     if(types.getTypeQName(clazz)!=null) {
424                         writeField(types,
425                                    propName,
426                                    null,
427                                    clazz,
428                                    false, false, all, false, null);
429                    
430                         done = true;
431                     }
432                 }
433                 if(!done) {
434                     writeField(types,
435                                propName,
436                                null,
437                                propertyDescriptor[i].getType(),
438                                propertyDescriptor[i].isIndexed(), false, all, false, null);
439                 }                    
440                 
441             }
442         }
443 
444         // done
445         return complexType;
446     }
447 
448     /**
449      * write a schema representation of the given Class field and append it to
450      * the where Node, recurse on complex types
451      * @param fieldName name of the field
452      * @param xmlType the schema type of the field
453      * @param fieldType type of the field
454      * @param isUnbounded causes maxOccurs="unbounded" if set
455      * @param where location for the generated schema node
456      * @param itemQName
457      * @throws Exception
458      */
459     protected void writeField(Types types,
460                               String fieldName,
461                               QName xmlType,
462                               Class fieldType,
463                               boolean isUnbounded,
464                               boolean isOmittable,
465                               Element where,
466                               boolean isAnonymous,
467                               QName itemQName) throws Exception {
468         Element elem;
469         String elementType = null;
470 
471         if (isAnonymous) {
472             elem = types.
473                     createElementWithAnonymousType(fieldName,
474                                                    fieldType,
475                                                    isOmittable,
476                                                    where.getOwnerDocument());
477         } else {
478             if (!SchemaUtils.isSimpleSchemaType(xmlType) &&
479                     Types.isArray(fieldType)) {
480                 xmlType = null;
481             }
482 
483             if (itemQName != null &&
484                     SchemaUtils.isSimpleSchemaType(xmlType) &&
485                     Types.isArray(fieldType)) {
486                 xmlType = null;
487             }
488 
489             QName typeQName = types.writeTypeAndSubTypeForPart(fieldType, xmlType);
490             elementType = types.getQNameString(typeQName);
491 
492             if (elementType == null) {
493                 // If writeType returns null, then emit an anytype.
494                 QName anyQN = Constants.XSD_ANYTYPE;
495                 String prefix = types.getNamespaces().
496                         getCreatePrefix(anyQN.getNamespaceURI());
497                 elementType = prefix + ":" + anyQN.getLocalPart();
498             }
499 
500             // isNillable default value depends on the field type
501             boolean isNillable = Types.isNullable(fieldType);
502             if (typeDesc != null) {
503                 FieldDesc field = typeDesc.getFieldByName(fieldName);
504                 if (field != null && field.isElement()) {
505                     isNillable = ((ElementDesc)field).isNillable();
506                 }
507             }
508 
509             elem = types.createElement(fieldName,
510                     elementType,
511                     isNillable,
512                     isOmittable,
513                     where.getOwnerDocument());
514         }
515 
516         if (isUnbounded) {
517             elem.setAttribute("maxOccurs", "unbounded");
518         }
519 
520         where.appendChild(elem);
521     }
522 
523     /**
524      * write aa attribute element and append it to the 'where' Node
525      * @param fieldName name of the field
526      * @param fieldType type of the field
527      * @param where location for the generated schema node
528      * @throws Exception
529      */
530     protected void writeAttribute(Types types,
531                                 String fieldName,
532                                 Class fieldType,
533                                 QName fieldXmlType,
534                                 Element where) throws Exception {
535 
536         // Attribute must be a simple type.
537         if (!types.isAcceptableAsAttribute(fieldType)) {
538             throw new AxisFault(Messages.getMessage("AttrNotSimpleType00",
539                                                      fieldName,
540                                                      fieldType.getName()));
541         }
542         Element elem = types.createAttributeElement(fieldName,
543                                            fieldType, fieldXmlType,
544                                            false,
545                                            where.getOwnerDocument());
546         where.appendChild(elem);
547     }
548 
549     /**
550      * Check for meta-data in the bean that will tell us if any of the
551      * properties are actually attributes, add those to the element
552      * attribute list
553      *
554      * @param value the object we are serializing
555      * @return attributes for this element, null if none
556      */
557     protected Attributes getObjectAttributes(Object value,
558                                            Attributes attributes,
559                                            SerializationContext context) {
560 
561         if (typeDesc == null || !typeDesc.hasAttributes())
562             return attributes;
563 
564         AttributesImpl attrs;
565         if (attributes == null) {
566             attrs = new AttributesImpl();
567         } else if (attributes instanceof AttributesImpl) {
568             attrs = (AttributesImpl)attributes;
569         } else {
570             attrs = new AttributesImpl(attributes);
571         }
572 
573         try {
574             // Find each property that is an attribute
575             // and add it to our attribute list
576             for (int i=0; i<propertyDescriptor.length; i++) {
577                 String propName = propertyDescriptor[i].getName();
578                 if (propName.equals("class"))
579                     continue;
580 
581                 FieldDesc field = typeDesc.getFieldByName(propName);
582                 // skip it if its not an attribute
583                 if (field == null || field.isElement())
584                     continue;
585 
586                 QName qname = field.getXmlName();
587                 if (qname == null) {
588                     qname = new QName("", propName);
589                 }
590 
591                 if (propertyDescriptor[i].isReadable() &&
592                     !propertyDescriptor[i].isIndexed()) {
593                     // add to our attributes
594                     Object propValue = propertyDescriptor[i].get(value);
595                     // Convert true/false to 1/0 in case of soapenv:mustUnderstand
596                     if (qname.equals(MUST_UNDERSTAND_QNAME)) {
597                       if (propValue.equals(Boolean.TRUE)) {
598                                 propValue = "1";
599                       } else if (propValue.equals(Boolean.FALSE)) {
600                         propValue = "0";
601                       }
602                     }
603                     // If the property value does not exist, don't serialize
604                     // the attribute.  In the future, the decision to serializer
605                     // the attribute may be more sophisticated.  For example, don't
606                     // serialize if the attribute matches the default value.
607                     if (propValue != null) {
608                         setAttributeProperty(propValue,
609                                              qname,
610                                              field.getXmlType(), 
611                                              attrs,
612                                              context);
613                     }
614                 }
615             }
616         } catch (Exception e) {
617             // no attributes
618             return attrs;
619         }
620 
621         return attrs;
622     }
623 
624     private void setAttributeProperty(Object propValue,
625                                       QName qname,
626                                       QName xmlType, AttributesImpl attrs,
627                                       SerializationContext context) throws Exception {
628 
629         String namespace = qname.getNamespaceURI();
630         String localName = qname.getLocalPart();
631 
632         // org.xml.sax.helpers.AttributesImpl JavaDoc says: "For the
633         // sake of speed, this method does no checking to see if the
634         // attribute is already in the list: that is the
635         // responsibility of the application." check for the existence
636         // of the attribute to avoid adding it more than once.
637         if (attrs.getIndex(namespace, localName) != -1) {
638             return;
639         }
640 
641         String propString = context.getValueAsString(propValue, xmlType);
642 
643         attrs.addAttribute(namespace,
644                            localName,
645                            context.attributeQName2String(qname),
646                            "CDATA",
647                            propString);
648     }
649 }