001 package org.andromda.cartridges.support.webservice.client;
002
003 import java.lang.reflect.Array;
004 import java.lang.reflect.Method;
005 import java.util.ArrayList;
006 import java.util.Collection;
007 import java.util.HashMap;
008 import java.util.Iterator;
009 import java.util.List;
010 import java.util.Map;
011 import javax.wsdl.Definition;
012 import javax.wsdl.Types;
013 import javax.wsdl.extensions.schema.Schema;
014 import javax.xml.namespace.QName;
015 import org.apache.axiom.om.OMAbstractFactory;
016 import org.apache.axiom.om.OMElement;
017 import org.apache.axiom.om.OMFactory;
018 import org.apache.axiom.om.OMNamespace;
019 import org.apache.axiom.om.util.Base64;
020 import org.apache.commons.beanutils.PropertyUtils;
021 import org.apache.commons.lang.StringUtils;
022 import org.w3c.dom.Element;
023 import org.w3c.dom.Node;
024 import org.w3c.dom.NodeList;
025
026 /**
027 * Utilities for Axis2 clients.
028 *
029 * @author Chad Brandon
030 */
031 public class Axis2ClientUtils
032 {
033 /**
034 * Gets the appropriate OM element for the given method
035 * @param definition the WSDL definition.
036 * @param method the method corresponding to the OMElement to create.
037 * @param arguments the arguments to pass to the method.
038 * @param typeMapper the {@link TypeMapper} instance to use for converting types.
039 * @return the constructed OMElement
040 */
041 public static OMElement getOperationOMElement(
042 final Definition definition,
043 final Method method,
044 final Object[] arguments,
045 final TypeMapper typeMapper)
046 {
047 final Class serviceClass = method.getDeclaringClass();
048 OMElement operationOMElement = null;
049 try
050 {
051 final String operationName = method.getName();
052 final String serviceName = serviceClass.getSimpleName();
053 final Element operationElement = getElementByAttribute(
054 definition,
055 NAME,
056 operationName);
057 final Schema operationSchema = Axis2ClientUtils.getElementSchemaByAttribute(
058 definition,
059 NAME,
060 operationName);
061 if (operationSchema != null)
062 {
063 final OMFactory factory = OMAbstractFactory.getOMFactory();
064
065 // - collect all the namespaces
066 final Map<String, OMNamespace> namespaces = new HashMap<String, OMNamespace>();
067 final OMNamespace xsiNamespace = factory.createOMNamespace(XSI_NS, XSI_PREFIX);
068 namespaces.put(XSI_PREFIX, xsiNamespace);
069
070 final Collection<Schema> schemas = getSchemas(definition);
071 for (final Schema schema : schemas)
072 {
073 final String namespace = Axis2ClientUtils.getTargetNamespace(schema);
074 final String prefix = getNamespacePrefix(
075 definition,
076 namespace);
077 namespaces.put(prefix, factory.createOMNamespace(namespace, prefix));
078 }
079
080 operationOMElement =
081 getOMElement(
082 definition,
083 operationSchema,
084 null,
085 null,
086 operationName,
087 factory,
088 namespaces,
089 typeMapper);
090 if (operationElement == null)
091 {
092 throw new RuntimeException("No operation with name '" + operationName + "' can be found on service: " +
093 serviceName);
094 }
095 final List argumentElements = Axis2ClientUtils.getElementsWithAttribute(
096 operationElement,
097 TYPE);
098 final Class[] parameterTypes = method.getParameterTypes();
099 if (argumentElements.size() != arguments.length)
100 {
101 throw new RuntimeException("Operation: " + operationName + " takes " + parameterTypes.length +
102 " argument(s), and 'arguments' only contains: " + arguments.length);
103 }
104
105 // - declare all the namespaces
106 operationOMElement.declareNamespace(xsiNamespace);
107 for (final OMNamespace namespace : namespaces.values())
108 {
109 operationOMElement.declareNamespace(namespace);
110 }
111
112 // - add the argument children
113 for (int ctr = 0; ctr < argumentElements.size(); ctr++)
114 {
115 final Element argument = (Element)argumentElements.get(ctr);
116 final String argumentName = getAttributeValue(
117 argument,
118 NAME);
119 final OMElement element = getOMElement(
120 definition,
121 operationSchema,
122 argument,
123 arguments[ctr],
124 argumentName,
125 factory,
126 namespaces,
127 typeMapper);
128 operationOMElement.addChild(element);
129 }
130 }
131 }
132 catch (Exception exception)
133 {
134 throw new RuntimeException(exception);
135 }
136 return operationOMElement;
137 }
138
139 /**
140 * Constructs and OMElement from the given bean
141 *
142 * @param definition the WSDL definition
143 * @param schema the current schema from which to retrieve the om element.
144 * @param componentElement the current componentElemnet of the WSDL definition.
145 * @param bean the bean to introspect
146 * @param elementName the name of the element to construct.
147 * @param factory the factory used for element construction.
148 * @param namespaces all available namespaces.
149 * @param typeMapper the {@link TypeMapper} instance to use for converting types.
150 * @return OMElement
151 */
152 public static OMElement getOMElement(
153 final Definition definition,
154 final Schema schema,
155 final Element componentElement,
156 final Object bean,
157 final String elementName,
158 final OMFactory factory,
159 final Map<String, OMNamespace> namespaces,
160 final TypeMapper typeMapper)
161 {
162 return getOMElement(
163 definition,
164 schema,
165 componentElement,
166 bean,
167 elementName,
168 factory,
169 namespaces,
170 typeMapper,
171 new ArrayList<Object>());
172 }
173
174 /**
175 * Constructs and OMElement from the given bean
176 *
177 * @param definition the WSDL definition
178 * @param schema the current schema from which to retrieve the om element.
179 * @param componentElement the current componentElemnet of the WSDL definition.
180 * @param bean the bean to introspect
181 * @param elementName the name of the element
182 * @param factory the OM factory instance used to create the element.
183 * @param namespaces all available namespaces.
184 * @param typeMapper the {@link TypeMapper} instance to use for converting types.
185 * @param evaluatingBeans the collection in which to keep the beans that are evaluating in order
186 * to prevent endless recursion.
187 */
188 private static OMElement getOMElement(
189 final Definition definition,
190 Schema schema,
191 final Element componentElement,
192 final Object bean,
193 final String elementName,
194 final OMFactory factory,
195 final Map<String, OMNamespace> namespaces,
196 final TypeMapper typeMapper,
197 final Collection<Object> evaluatingBeans)
198 {
199 final String componentElementName = componentElement != null ? componentElement.getAttribute(NAME) : null;
200 if (StringUtils.isNotBlank(componentElementName))
201 {
202 final Schema currentSchema = Axis2ClientUtils.getElementSchemaByAttribute(
203 definition,
204 NAME,
205 componentElementName);
206 if (currentSchema != null)
207 {
208 schema = currentSchema;
209 }
210 }
211
212 OMNamespace omNamespace = null;
213 if (isQualified(schema))
214 {
215 final String namespace = Axis2ClientUtils.getTargetNamespace(schema);
216 final String namespacePrefix = getNamespacePrefix(
217 definition,
218 namespace);
219 omNamespace = namespaces.get(namespacePrefix);
220 }
221
222 final OMElement omElement =
223 factory.createOMElement(
224 elementName,
225 omNamespace);
226 if (bean != null && evaluatingBeans != null && !evaluatingBeans.contains(bean))
227 {
228 evaluatingBeans.add(bean);
229 if (isSimpleType(bean, typeMapper))
230 {
231 omElement.addChild(factory.createOMText(typeMapper.getStringValue(bean)));
232 }
233 else if (bean instanceof byte[])
234 {
235 omElement.addChild(factory.createOMText(Base64.encode((byte[])bean)));
236 }
237 else
238 {
239 final Element currentComponentElement =
240 Axis2ClientUtils.getElementByAttribute(
241 definition,
242 NAME,
243 bean.getClass().getSimpleName());
244 final Class beanType = bean.getClass();
245 if (beanType.isArray())
246 {
247 final Element arrayElement = Axis2ClientUtils.getRequiredElementByAttribute(
248 definition,
249 componentElement,
250 NAME,
251 elementName);
252 final Element arrayTypeElement =
253 Axis2ClientUtils.getElementByAttribute(
254 definition,
255 NAME,
256 stripPrefix(arrayElement.getAttribute(TYPE)));
257 final String arrayComponentName = Axis2ClientUtils.getAttributeValueFromChildElement(
258 arrayTypeElement,
259 NAME,
260 0);
261 for (int ctr = 0; ctr < Array.getLength(bean); ctr++)
262 {
263 omElement.addChild(
264 getOMElement(
265 definition,
266 schema,
267 currentComponentElement,
268 Array.get(
269 bean,
270 ctr),
271 arrayComponentName,
272 factory,
273 namespaces,
274 typeMapper,
275 evaluatingBeans));
276 }
277 }
278 else
279 {
280 final String attributeValue = omNamespace != null ?
281 omNamespace.getPrefix() + NS_SEPARATOR + beanType.getSimpleName() : beanType.getSimpleName();
282 // - add the xsi:type attribute for complex types
283 omElement.addAttribute(TYPE, attributeValue, namespaces.get(XSI_PREFIX));
284 }
285 try
286 {
287 final java.util.Map<String, Object> properties = PropertyUtils.describe(bean);
288 for (final String name : properties.keySet())
289 {
290 if (!CLASS.equals(name))
291 {
292 final Object value = properties.get(name);
293 if (value != null)
294 {
295 omElement.addChild(
296 getOMElement(
297 definition,
298 schema,
299 currentComponentElement,
300 value,
301 name,
302 factory,
303 namespaces,
304 typeMapper,
305 evaluatingBeans));
306 }
307 }
308 }
309 }
310 catch (final Throwable throwable)
311 {
312 throw new RuntimeException(throwable);
313 }
314 }
315 evaluatingBeans.remove(bean);
316 }
317 return omElement;
318 }
319
320 private static final String ELEMENT_FORM_DEFAULT = "elementFormDefault";
321
322 /**
323 * Indicates whether or not a xml document is qualified.
324 */
325 private static final String QUALIFIED = "qualified";
326
327 /**
328 * The attribute that stores the target namespace.
329 */
330 private static final String TARGET_NAMESPACE = "targetNamespace";
331
332 /**
333 * The schema instance namespace.
334 */
335 private static final String XSI_NS = "http://www.w3.org/2001/XMLSchema-instance";
336
337 /**
338 * The prefix for the schema instance namespace.
339 */
340 private static final String XSI_PREFIX = "xsi";
341
342 /**
343 * Used to separate a namespace prefix and name in a QName.
344 */
345 private static final String NS_SEPARATOR = ":";
346
347 /**
348 * Indicates whether or not the schema is qualified.
349 *
350 * @param schema the schema to check.
351 * @return true/false
352 */
353 private static boolean isQualified(Schema schema)
354 {
355 boolean isQualified = false;
356 if (schema != null)
357 {
358 final String qualified = Axis2ClientUtils.getAttributeValue(
359 schema.getElement(),
360 ELEMENT_FORM_DEFAULT);
361 isQualified = QUALIFIED.equalsIgnoreCase(qualified);
362 }
363 return isQualified;
364 }
365
366 private static String getTargetNamespace(Schema schema)
367 {
368 return Axis2ClientUtils.getAttributeValue(
369 schema.getElement(),
370 TARGET_NAMESPACE);
371 }
372
373 private static String stripPrefix(String name)
374 {
375 return name.replaceAll(
376 ".*:",
377 "");
378 }
379
380 private static String getNamespacePrefix(
381 final Definition definition,
382 final String namespace)
383 {
384 String prefix = null;
385 final Map<String, String> namespaces = definition.getNamespaces();
386 for (Map.Entry<String, String> entry : namespaces.entrySet())
387 {
388 if (entry.getValue().equals(namespace))
389 {
390 prefix = entry.getKey();
391 }
392 }
393 return prefix;
394 }
395
396 private static final String NAME = "name";
397 private static final String TYPE = "type";
398 private static final String BASE = "base";
399
400 private static Element getRequiredElementByAttribute(
401 final Definition definition,
402 final Element container,
403 final String attribute,
404 final String value)
405 {
406 Element element = null;
407 if (container != null)
408 {
409 element = Axis2ClientUtils.getElementByAttribute(
410 container,
411 attribute,
412 value);
413 }
414 // - try finding the element on an inherited type
415 if (element == null)
416 {
417 final String xsiTypeName = Axis2ClientUtils.getAttributeValueFromChildElement(container, BASE, 0);
418 final String typeName = getLocalName(xsiTypeName);
419 element = Axis2ClientUtils.getElementByAttribute(
420 Axis2ClientUtils.getElementByAttribute(definition, NAME, typeName),
421 attribute,
422 value);
423 }
424 if (element == null)
425 {
426 String attr = container==null ? "" : container.getAttribute(NAME);
427 throw new RuntimeException('\'' + value + "' was not found on element '" + attr + '\'');
428 }
429 return element;
430 }
431
432 private static Element getElementByAttribute(
433 final Definition definition,
434 final String attribute,
435 final String value)
436 {
437 Element element = null;
438 if (value != null)
439 {
440 final Types types = definition.getTypes();
441 final Collection elements = types.getExtensibilityElements();
442 for (final Iterator iterator = elements.iterator(); iterator.hasNext();)
443 {
444 final Object object = iterator.next();
445 if (object instanceof Schema)
446 {
447 final Schema schema = (Schema)object;
448 element =
449 getElementByAttribute(
450 schema.getElement(),
451 attribute,
452 value);
453 if (element != null)
454 {
455 break;
456 }
457 }
458 }
459 }
460 return element;
461 }
462
463 private static Collection<Schema> getSchemas(final Definition definition)
464 {
465 final Collection<Schema> schemas = new ArrayList<Schema>();
466 final Types types = definition.getTypes();
467 final Collection elements = types.getExtensibilityElements();
468 for (final Iterator iterator = elements.iterator(); iterator.hasNext();)
469 {
470 final Object object = iterator.next();
471 if (object instanceof Schema)
472 {
473 schemas.add((Schema)object);
474 }
475 }
476 return schemas;
477 }
478
479 private static Element getElementByAttribute(
480 final Schema schema,
481 final String attribute,
482 final String value)
483 {
484 return getElementByAttribute(
485 schema.getElement(),
486 attribute,
487 value);
488 }
489
490 /**
491 * Gets the schema that owns the element that has the given attribute with the given value.
492 *
493 * @param definition the WSDL definition.
494 * @param attribute the name of the attribute to find.
495 * @param value the value of the attribute.
496 * @return the schema
497 */
498 private static Schema getElementSchemaByAttribute(
499 final Definition definition,
500 final String attribute,
501 final String value)
502 {
503 final Types types = definition.getTypes();
504 final Collection elements = types.getExtensibilityElements();
505 Schema schema = null;
506 for (final Iterator iterator = elements.iterator(); iterator.hasNext();)
507 {
508 final Object object = iterator.next();
509 if (object instanceof Schema)
510 {
511 final Element element = getElementByAttribute(
512 (Schema)object,
513 attribute,
514 value);
515 if (element != null)
516 {
517 schema = (Schema)object;
518 break;
519 }
520 }
521 }
522 return schema;
523 }
524
525 private static Element getElementByAttribute(
526 final Element element,
527 final String attribute,
528 final String value)
529 {
530 Element found = null;
531 if (element != null && value != null)
532 {
533 final String foundAttribute = element.getAttribute(attribute);
534 if (StringUtils.isNotBlank(value) && value.equals(foundAttribute))
535 {
536 found = element;
537 }
538 else
539 {
540 final NodeList nodes = element.getChildNodes();
541 for (int ctr = 0; ctr < nodes.getLength(); ctr++)
542 {
543 final Node node = nodes.item(ctr);
544 if (node instanceof Element)
545 {
546 found =
547 getElementByAttribute(
548 (Element)node,
549 attribute,
550 value);
551 if (found != null)
552 {
553 break;
554 }
555 }
556 }
557 }
558 }
559 return found;
560 }
561
562 private static List<Element> getElementsWithAttribute(
563 final Element element,
564 final String attribute)
565 {
566 final List<Element> found = new ArrayList<Element>();
567 if (element != null)
568 {
569 final String foundAttribute = element.getAttribute(attribute);
570 if (StringUtils.isNotBlank(foundAttribute))
571 {
572 found.add(element);
573 }
574 final NodeList nodes = element.getChildNodes();
575 for (int ctr = 0; ctr < nodes.getLength(); ctr++)
576 {
577 final Node node = nodes.item(ctr);
578 if (node instanceof Element)
579 {
580 found.addAll(getElementsWithAttribute(
581 (Element)node,
582 attribute));
583 }
584 }
585 }
586 return found;
587 }
588
589 private static String getAttributeValue(
590 final Element element,
591 final String attribute)
592 {
593 String value = null;
594 Element found = getElementWithAttribute(
595 element,
596 attribute);
597 if (found != null)
598 {
599 value = found.getAttribute(attribute);
600 }
601 return value;
602 }
603
604 private static String getAttributeValueFromChildElement(
605 final Element element,
606 final String attribute,
607 int childIndex)
608 {
609 String value = null;
610 if (element != null)
611 {
612 int elementIndex = 0;
613 for (int ctr = 0; ctr < element.getChildNodes().getLength(); ctr++)
614 {
615 final Node node = element.getChildNodes().item(ctr);
616 if (node instanceof Element)
617 {
618 if (elementIndex == childIndex)
619 {
620 value =
621 getAttributeValue(
622 (Element)node,
623 attribute);
624 }
625 elementIndex++;
626 }
627 }
628 }
629 return value;
630 }
631
632 /**
633 * Finds the first child element of the given <code>element</code> that has an attribute
634 * matching the name of <code>attribute</code>.
635 *
636 * @param element the element to search.
637 * @param attribute the name of the attribute to get.
638 * @return the found element or null if not found.
639 */
640 private static Element getElementWithAttribute(
641 final Element element,
642 final String attribute)
643 {
644 Element found = null;
645 if (element != null)
646 {
647 final NodeList nodes = element.getChildNodes();
648 final String foundAttribute = element.getAttribute(attribute);
649 if (StringUtils.isNotBlank(foundAttribute))
650 {
651 found = element;
652 }
653 if (found == null)
654 {
655 for (int ctr = 0; ctr < nodes.getLength(); ctr++)
656 {
657 final Node node = nodes.item(ctr);
658 if (node instanceof Element)
659 {
660 found =
661 getElementWithAttribute(
662 (Element)node,
663 attribute);
664 }
665 }
666 }
667 }
668 return found;
669 }
670
671 private static final String CLASS = "class";
672
673 /**
674 * Deserializes the given <code>element</code> to the given <code>type</code>.
675 *
676 * @param element the XML OMElement
677 * @param type the java type.
678 * @param typeMapper the "object creator" used to construct objects from given classes.
679 * @return the deserialized object.
680 * @throws Exception
681 */
682 public static Object deserialize(
683 OMElement element,
684 Class type,
685 final TypeMapper typeMapper) throws Exception
686 {
687 Object bean = null;
688 if (typeMapper.isSimpleType(type))
689 {
690 bean = getSimpleTypeObject(type, element, typeMapper);
691 }
692 else if (type == byte[].class)
693 {
694 bean = Base64.decode(element.getText());
695 }
696 else
697 {
698 if (type.isArray())
699 {
700 final Collection<Object> elements = new ArrayList<Object>();
701 for (final Iterator iterator = element.getChildElements(); iterator.hasNext();)
702 {
703 OMElement omElement = (OMElement)iterator.next();
704 elements.add(deserialize(
705 omElement,
706 type.getComponentType(),
707 typeMapper));
708 }
709 bean =
710 elements.toArray((Object[])Array.newInstance(
711 type.getComponentType(),
712 0));
713 }
714 else
715 {
716 try
717 {
718 type = getAppropriateType(element, type);
719 bean = typeMapper.getObject(type);
720 final java.beans.PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(type);
721 for (int ctr = 0; ctr < descriptors.length; ctr++)
722 {
723 final java.beans.PropertyDescriptor descriptor = descriptors[ctr];
724 final String name = descriptor.getName();
725 if (!CLASS.equals(name))
726 {
727 final OMElement propertyElement = findElementByName(
728 element,
729 name);
730
731 if (propertyElement != null)
732 {
733 PropertyUtils.setProperty(
734 bean,
735 name,
736 deserialize(
737 propertyElement,
738 descriptor.getPropertyType(),
739 typeMapper));
740 }
741 }
742 }
743 }
744 catch (final Throwable throwable)
745 {
746 throw new RuntimeException(throwable);
747 }
748 }
749 }
750 return bean;
751 }
752
753 /**
754 * Gets the object given the type, element and typeMapper.
755 * @param type the type of object to get.
756 * @param element the elemtn that has the value to populate the object with.
757 * @param typeMapper the mapper used for retriving the value if it can't be found
758 * by the simple type mapper.
759 * @return the object
760 */
761 private static Object getSimpleTypeObject(Class type,
762 OMElement element, TypeMapper typeMapper)
763 {
764 Object object = org.apache.axis2.databinding.typemapping.SimpleTypeMapper.getSimpleTypeObject(
765 type,
766 element);
767 if (object == null && element != null)
768 {
769 object = typeMapper.getObject(type, element.getText());
770 }
771 return object;
772 }
773
774 /**
775 * The java package separator character.
776 */
777 private static final String PACKAGE_SEPARATOR = ".";
778
779 /**
780 * The xsi:type Qname.
781 */
782 private static final QName XSI_TYPE_QNAME = new QName(XSI_NS, TYPE);
783
784 /**
785 * Gets the appropriate type from checking the xsi:type (if present). Currently
786 * this just assumes any types in a hierarchy are in the same package.
787 *
788 * @param element the element from which to retrieve the type.
789 * @param type the current type.
790 * @return the appropriate type.
791 * @throws ClassNotFoundException
792 */
793 private static Class getAppropriateType(final OMElement element, Class type) throws ClassNotFoundException
794 {
795 final String xsiTypeName = element.getAttributeValue(XSI_TYPE_QNAME);
796 if (xsiTypeName != null)
797 {
798 final String typeName = getLocalName(xsiTypeName);
799 if (!typeName.equals(type.getSimpleName()))
800 {
801 // TODO: need to handle types that aren't in the same package (we should look up the
802 // mapped class here instead of assuming the same package)
803 type = Thread.currentThread().getContextClassLoader().loadClass(
804 type.getPackage().getName() + PACKAGE_SEPARATOR + typeName);
805 }
806 }
807 return type;
808 }
809
810 /**
811 * Strips the prefix from a type name in the given form: prefix:localName
812 * to get the local name.
813 *
814 * @param typeName the type name with an optional prefix
815 * @return the local name.
816 */
817 private static String getLocalName(final String typeName)
818 {
819 String localName;
820 String[] names = typeName.split(NS_SEPARATOR);
821 if (names.length > 1)
822 {
823 localName = names[1];
824 }
825 else
826 {
827 localName = names[0];
828 }
829 return localName;
830 }
831
832 /**
833 * Finds an element having the given <code>name</code> from the child elements on the given
834 * <code>element</code>.
835 *
836 * @param element the element to search.
837 * @param name the name of the element to find.
838 * @return the found element or null if one couldn't be found.
839 */
840 private static OMElement findElementByName(
841 final OMElement element,
842 final String name)
843 {
844 OMElement found = null;
845 for (final Iterator iterator = element.getChildElements(); iterator.hasNext();)
846 {
847 final OMElement child = (OMElement)iterator.next();
848 if (child.getLocalName().equals(name))
849 {
850 found = child;
851 break;
852 }
853 }
854 return found;
855 }
856
857 /**
858 * First delegate to the Axis2 simple type mapper, if that says
859 * its simple, it is, otherwise check to see if the type is an enumeration (typesafe
860 * or Java5 version).
861 *
862 * @param bean the bean to check.
863 * @return true/false
864 */
865 private static boolean isSimpleType(final Object bean, final TypeMapper typeMapper)
866 {
867 return typeMapper.isSimpleType(bean != null ? bean.getClass() : null);
868 }
869 }