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 <types> element of a WSDL document, or underneath an
289 * <element> or <attribute> 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 }