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 <xsd:any> 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}