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