/** * * Copyright 2005 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.geronimo.axis.builder; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.rpc.encoding.DeserializerFactory; import javax.xml.rpc.encoding.SerializerFactory; import org.apache.axis.description.AttributeDesc; import org.apache.axis.description.ElementDesc; import org.apache.axis.description.FieldDesc; import org.apache.axis.description.OperationDesc; import org.apache.axis.description.ParameterDesc; import org.apache.axis.encoding.DefaultJAXRPC11TypeMappingImpl; import org.apache.axis.encoding.DefaultSOAPEncodingTypeMappingImpl; import org.apache.axis.encoding.TypeMappingImpl; import org.apache.axis.encoding.XMLType; import org.apache.axis.encoding.ser.ArrayDeserializerFactory; import org.apache.axis.encoding.ser.ArraySerializerFactory; import org.apache.axis.encoding.ser.BeanDeserializerFactory; import org.apache.axis.encoding.ser.BeanSerializerFactory; import org.apache.axis.encoding.ser.EnumDeserializerFactory; import org.apache.axis.encoding.ser.EnumSerializerFactory; import org.apache.axis.encoding.ser.SimpleListDeserializerFactory; import org.apache.axis.encoding.ser.SimpleListSerializerFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.geronimo.axis.client.ArrayTypeInfo; import org.apache.geronimo.axis.client.TypeInfo; import org.apache.geronimo.common.DeploymentException; import org.apache.geronimo.kernel.ClassLoading; import org.apache.geronimo.xbeans.j2ee.JavaWsdlMappingType; import org.apache.geronimo.xbeans.j2ee.JavaXmlTypeMappingType; import org.apache.geronimo.xbeans.j2ee.VariableMappingType; import org.apache.xmlbeans.SchemaLocalAttribute; import org.apache.xmlbeans.SchemaParticle; import org.apache.xmlbeans.SchemaProperty; import org.apache.xmlbeans.SchemaType; import org.apache.xmlbeans.soap.SOAPArrayType; import org.apache.xmlbeans.soap.SchemaWSDLArrayType; /** * @version $Rev$ $Date$ */ public class HeavyweightTypeInfoBuilder implements TypeInfoBuilder { private static final String SOAP_ENCODING_NS = "http://schemas.xmlsoap.org/soap/encoding/"; private static final String XML_SCHEMA_NS = "http://www.w3.org/2001/XMLSchema"; private static final Log log = LogFactory.getLog(HeavyweightTypeInfoBuilder.class); private final ClassLoader cl; private final Map schemaTypeKeyToSchemaTypeMap; private final Set wrapperElementQNames; private final Collection operations; private final boolean hasEncoded; public HeavyweightTypeInfoBuilder(ClassLoader cl, Map schemaTypeKeyToSchemaTypeMap, Set wrapperElementQNames, Collection operations, boolean hasEncoded) { this.cl = cl; this.schemaTypeKeyToSchemaTypeMap = schemaTypeKeyToSchemaTypeMap; this.wrapperElementQNames = wrapperElementQNames; this.operations = operations; this.hasEncoded = hasEncoded; } public List buildTypeInfo(JavaWsdlMappingType mapping) throws DeploymentException { List typeInfoList = new ArrayList(); Set mappedTypeQNames = new HashSet(); JavaXmlTypeMappingType[] javaXmlTypeMappings = mapping.getJavaXmlTypeMappingArray(); for (int j = 0; j < javaXmlTypeMappings.length; j++) { JavaXmlTypeMappingType javaXmlTypeMapping = javaXmlTypeMappings[j]; SchemaTypeKey key; boolean isElement = javaXmlTypeMapping.getQnameScope().getStringValue().equals("element"); boolean isSimpleType = javaXmlTypeMapping.getQnameScope().getStringValue().equals("simpleType"); if (javaXmlTypeMapping.isSetRootTypeQname()) { QName typeQName = javaXmlTypeMapping.getRootTypeQname().getQNameValue(); key = new SchemaTypeKey(typeQName, isElement, isSimpleType, false, null); // Skip the wrapper elements. if (wrapperElementQNames.contains(typeQName)) { continue; } } else if (javaXmlTypeMapping.isSetAnonymousTypeQname()) { String anonTypeQNameString = javaXmlTypeMapping.getAnonymousTypeQname().getStringValue(); int pos = anonTypeQNameString.lastIndexOf(":"); if (pos == -1) { throw new DeploymentException("anon QName is invalid, no final ':' " + anonTypeQNameString); } //this appears to be ignored... QName typeQName = new QName(anonTypeQNameString.substring(0, pos), anonTypeQNameString.substring(pos + 1)); key = new SchemaTypeKey(typeQName, isElement, isSimpleType, true, null); // Skip the wrapper elements. if (wrapperElementQNames.contains(new QName(anonTypeQNameString.substring(0, pos), anonTypeQNameString.substring(pos + 2)))) { continue; } } else { throw new DeploymentException("either root type qname or anonymous type qname must be set"); } SchemaType schemaType = (SchemaType) schemaTypeKeyToSchemaTypeMap.get(key); if (schemaType == null) { // if it is a built-in type, then one assumes a redundant mapping. if (null != TypeMappingLookup.getFactoryPair(key.getqName())) { continue; } // throw new DeploymentException("Schema type key " + key + " not found in analyzed schema: " + schemaTypeKeyToSchemaTypeMap); log.warn("Schema type key " + key + " not found in analyzed schema: " + schemaTypeKeyToSchemaTypeMap); continue; } mappedTypeQNames.add(key.getqName()); String className = javaXmlTypeMapping.getJavaType().getStringValue().trim(); Class clazz = null; try { clazz = ClassLoading.loadClass(className, cl); } catch (ClassNotFoundException e2) { throw new DeploymentException("Could not load java type", e2); } TypeInfo.UpdatableTypeInfo internalTypeInfo = defineSerializerPair(schemaType, clazz); populateInternalTypeInfo(clazz, key, schemaType, javaXmlTypeMapping, internalTypeInfo); typeInfoList.add(internalTypeInfo.buildTypeInfo()); } Map qNameToKey = new HashMap(); for (Iterator iter = schemaTypeKeyToSchemaTypeMap.keySet().iterator(); iter.hasNext();) { SchemaTypeKey key = (SchemaTypeKey) iter.next(); qNameToKey.put(key.getqName(), key); } for (Iterator iter = operations.iterator(); iter.hasNext();) { OperationDesc operationDesc = (OperationDesc) iter.next(); ArrayList parameters = new ArrayList(operationDesc.getParameters()); ParameterDesc returnParameterDesc = operationDesc.getReturnParamDesc(); if (null != returnParameterDesc.getTypeQName() && false == returnParameterDesc.getTypeQName().equals(XMLType.AXIS_VOID)) { parameters.add(returnParameterDesc); } for (Iterator iterator = parameters.iterator(); iterator.hasNext();) { ParameterDesc parameterDesc = (ParameterDesc) iterator.next(); QName typeQName = parameterDesc.getTypeQName(); if (null == typeQName) { continue; } else if (mappedTypeQNames.contains(typeQName)) { continue; } else if (typeQName.getNamespaceURI().equals(XML_SCHEMA_NS) || typeQName.getNamespaceURI().equals(SOAP_ENCODING_NS)) { continue; } SchemaTypeKey key = (SchemaTypeKey) qNameToKey.get(typeQName); if (null == key) { log.warn("Type QName [" + typeQName + "] defined by operation [" + operationDesc + "] has not been found in schema: " + schemaTypeKeyToSchemaTypeMap); continue; } SchemaType schemaType = (SchemaType) schemaTypeKeyToSchemaTypeMap.get(key); mappedTypeQNames.add(key.getqName()); if (false == schemaType.isSimpleType()) { if (false == parameterDesc.getJavaType().isArray()) { if (false == mappedTypeQNames.contains(schemaType.getName())) { // TODO: this lookup is not enough: the jaxrpc mapping file may define an anonymous // mapping. log.warn("Operation [" + operationDesc + "] uses XML type [" + schemaType + "], whose mapping is not declared by the jaxrpc mapping file.\n Continuing deployment; " + "yet, the deployment is not-portable."); } continue; } } Class clazz = parameterDesc.getJavaType(); TypeInfo.UpdatableTypeInfo internalTypeInfo = defineSerializerPair(schemaType, clazz); setTypeQName(internalTypeInfo, key); internalTypeInfo.setFields(new FieldDesc[0]); typeInfoList.add(internalTypeInfo.buildTypeInfo()); } } return typeInfoList; } private TypeInfo.UpdatableTypeInfo defineSerializerPair(SchemaType schemaType, Class clazz) throws DeploymentException { TypeInfo.UpdatableTypeInfo internalTypeInfo = new TypeInfo.UpdatableTypeInfo(); Class serializerFactoryClass = null; Class deserializerFactoryClass = null; if (schemaType.isSimpleType()) { if (SchemaType.ATOMIC == schemaType.getSimpleVariety()) { if (clazz.isArray()) { internalTypeInfo = new ArrayTypeInfo.UpdatableArrayTypeInfo(); serializerFactoryClass = ArraySerializerFactory.class; deserializerFactoryClass = ArrayDeserializerFactory.class; //TODO set componentType, componentQName } else if (null != schemaType.getEnumerationValues()) { serializerFactoryClass = EnumSerializerFactory.class; deserializerFactoryClass = EnumDeserializerFactory.class; } else { QName typeQName = schemaType.getPrimitiveType().getName(); FactoryPair pair = (FactoryPair) TypeMappingLookup.getFactoryPair(typeQName); if (null == pair) { throw new DeploymentException("Primitive type [" + typeQName + "] is not registered."); } serializerFactoryClass = pair.serializerFactoryClass; deserializerFactoryClass = pair.deserializerFactoryClass; } } else if (SchemaType.LIST == schemaType.getSimpleVariety()) { serializerFactoryClass = SimpleListSerializerFactory.class; deserializerFactoryClass = SimpleListDeserializerFactory.class; } else { throw new DeploymentException("Schema type [" + schemaType + "] is invalid."); } } else { if (clazz.isArray()) { internalTypeInfo = new ArrayTypeInfo.UpdatableArrayTypeInfo(); serializerFactoryClass = ArraySerializerFactory.class; deserializerFactoryClass = ArrayDeserializerFactory.class; QName componentType = null; //First, handle case that looks like this: // <complexType name="ArrayOfstring"> // <complexContent> // <restriction base="soapenc:Array"> // <attribute ref="soapenc:arrayType" wsdl:arrayType="xsd:string[]"/> // </restriction> // </complexContent> // </complexType> SchemaLocalAttribute arrayTypeAttribute = schemaType.getAttributeModel().getAttribute(new QName(SOAP_ENCODING_NS, "arrayType")); if (arrayTypeAttribute != null) { SchemaWSDLArrayType wsdlArrayType = (SchemaWSDLArrayType) arrayTypeAttribute; SOAPArrayType soapArrayType = wsdlArrayType.getWSDLArrayType(); if (soapArrayType != null) { componentType = soapArrayType.getQName(); log.debug("extracted componentType " + componentType + " from schemaType " + schemaType); } else { log.info("no SOAPArrayType for component from schemaType " + schemaType); } } else { log.warn("No soap array info for schematype: " + schemaType); } if (componentType == null) { //If that didn't work, try to handle case like this: // <complexType name="ArrayOfstring1"> // <complexContent> // <restriction base="soapenc:Array"> // <sequence> // <element name="string1" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/> // </sequence> // </restriction> // </complexContent> // </complexType> //todo consider if we should check for maxOccurs > 1 if (schemaType.getBaseType().getName().equals(new QName(SOAP_ENCODING_NS, "Array"))) { SchemaProperty[] properties = schemaType.getDerivedProperties(); if (properties.length != 1) { throw new DeploymentException("more than one element inside array definition: " + schemaType); } componentType = properties[0].getType().getName(); log.debug("determined component type from element type"); } } ((ArrayTypeInfo.UpdatableArrayTypeInfo)internalTypeInfo).setComponentType(componentType); //If we understand the axis comments correctly, componentQName is never set for j2ee ws. } else { QName typeQName; if (SchemaType.SIMPLE_CONTENT == schemaType.getContentType()) { typeQName = schemaType.getBaseType().getName(); } else if (SchemaType.EMPTY_CONTENT == schemaType.getContentType() || SchemaType.ELEMENT_CONTENT == schemaType.getContentType() || SchemaType.MIXED_CONTENT == schemaType.getContentType()) { typeQName = schemaType.getName(); } else { throw new DeploymentException("Schema type [" + schemaType + "] is invalid."); } FactoryPair pair = (FactoryPair) TypeMappingLookup.getFactoryPair(typeQName); if (null != pair) { serializerFactoryClass = pair.serializerFactoryClass; deserializerFactoryClass = pair.deserializerFactoryClass; } else { serializerFactoryClass = BeanSerializerFactory.class; deserializerFactoryClass = BeanDeserializerFactory.class; } } } internalTypeInfo.setClazz(clazz); internalTypeInfo.setSerializerClass(serializerFactoryClass); internalTypeInfo.setDeserializerClass(deserializerFactoryClass); return internalTypeInfo; } private void setTypeQName(TypeInfo.UpdatableTypeInfo typeInfo, SchemaTypeKey key) { //figure out the name axis expects to look up under. QName axisKey = key.getElementQName(); if (axisKey == null) { axisKey = key.getqName(); } typeInfo.setQName(axisKey); } private void populateInternalTypeInfo(Class javaClass, SchemaTypeKey key, SchemaType schemaType, JavaXmlTypeMappingType javaXmlTypeMapping, TypeInfo.UpdatableTypeInfo typeInfo) throws DeploymentException { String ns = key.getqName().getNamespaceURI(); typeInfo.setCanSearchParents(schemaType.getDerivationType() == SchemaType.DT_RESTRICTION); setTypeQName(typeInfo, key); Map paramNameToType = new HashMap(); if (null == schemaType.getContentModel()) { ; } else if (SchemaParticle.SEQUENCE == schemaType.getContentModel().getParticleType() || SchemaParticle.ALL == schemaType.getContentModel().getParticleType()) { SchemaParticle[] properties = schemaType.getContentModel().getParticleChildren(); for (int i = 0; i < properties.length; i++) { SchemaParticle parameter = properties[i]; paramNameToType.put(parameter.getName(), parameter); } } else if (SchemaParticle.ELEMENT == schemaType.getContentModel().getParticleType()) { SchemaParticle parameter = schemaType.getContentModel(); paramNameToType.put(parameter.getName(), parameter); } else { throw new DeploymentException("Only element, sequence, and all particle types are supported." + " SchemaType name =" + schemaType.getName()); } Map attNameToType = new HashMap(); if (null != schemaType.getAttributeModel()) { SchemaLocalAttribute[] attributes = schemaType.getAttributeModel().getAttributes(); for (int i = 0; i < attributes.length; i++) { SchemaLocalAttribute attribute = attributes[i]; Object old = attNameToType.put(attribute.getName().getLocalPart(), attribute); if (old != null) { throw new DeploymentException("Complain to your expert group member, spec does not support attributes with the same local name and differing namespaces: original: " + old + ", duplicate local name: " + attribute); } } } VariableMappingType[] variableMappings = javaXmlTypeMapping.getVariableMappingArray(); // short-circuit the processing of arrays as they should not define variable-mapping elements. if (javaClass.isArray()) { if (0 != variableMappings.length) { // for portability reason we simply warn and not fail. log.warn("Ignoring variable-mapping defined for class " + javaClass + " which is an array."); } typeInfo.setFields(new FieldDesc[0]); return; } FieldDesc[] fields = new FieldDesc[variableMappings.length]; typeInfo.setFields(fields); PropertyDescriptor[] propertyDescriptors = new PropertyDescriptor[0]; try { propertyDescriptors = Introspector.getBeanInfo(javaClass).getPropertyDescriptors(); } catch (IntrospectionException e) { throw new DeploymentException("Class " + javaClass + " is not a valid javabean", e); } Map properties = new HashMap(); for (int i = 0; i < propertyDescriptors.length; i++) { PropertyDescriptor propertyDescriptor = propertyDescriptors[i]; properties.put(propertyDescriptor.getName(), propertyDescriptor.getPropertyType()); } for (int i = 0; i < variableMappings.length; i++) { VariableMappingType variableMapping = variableMappings[i]; String fieldName = variableMapping.getJavaVariableName().getStringValue().trim(); if (variableMapping.isSetXmlAttributeName()) { AttributeDesc attributeDesc = new AttributeDesc(); attributeDesc.setFieldName(fieldName); Class javaType = (Class) properties.get(fieldName); if (javaType == null) { throw new DeploymentException("field name " + fieldName + " not found in " + properties); } String attributeLocalName = variableMapping.getXmlAttributeName().getStringValue().trim(); QName xmlName = new QName("", attributeLocalName); attributeDesc.setXmlName(xmlName); SchemaLocalAttribute attribute = (SchemaLocalAttribute) attNameToType.get(attributeLocalName); if (null == attribute) { throw new DeploymentException("attribute " + xmlName + " not found in schema " + schemaType.getName()); } attributeDesc.setXmlType(attribute.getType().getName()); fields[i] = attributeDesc; } else { ElementDesc elementDesc = new ElementDesc(); elementDesc.setFieldName(fieldName); Class javaType = (Class) properties.get(fieldName); if (javaType == null) { //see if it is a public field try { Field field = javaClass.getField(fieldName); javaType = field.getType(); } catch (NoSuchFieldException e) { throw new DeploymentException("field name " + fieldName + " not found in " + properties); } } QName xmlName = new QName("", variableMapping.getXmlElementName().getStringValue().trim()); SchemaParticle particle = (SchemaParticle) paramNameToType.get(xmlName); if (null == particle) { xmlName = new QName(ns, variableMapping.getXmlElementName().getStringValue().trim()); particle = (SchemaParticle) paramNameToType.get(xmlName); if (null == particle) { throw new DeploymentException("element " + xmlName + " not found in schema " + schemaType.getName()); } } else if (SchemaParticle.ELEMENT != particle.getParticleType()) { throw new DeploymentException(xmlName + " is not an element in schema " + schemaType.getName()); } elementDesc.setNillable(particle.isNillable() || hasEncoded); elementDesc.setXmlName(xmlName); if (null != particle.getType().getName()) { elementDesc.setXmlType(particle.getType().getName()); } else { QName anonymousName; if (key.isAnonymous()) { anonymousName = new QName(key.getqName().getNamespaceURI(), key.getqName().getLocalPart() + ">" + particle.getName().getLocalPart()); } else { anonymousName = new QName(key.getqName().getNamespaceURI(), ">" + key.getqName().getLocalPart() + ">" + particle.getName().getLocalPart()); } elementDesc.setXmlType(anonymousName); } if (javaType.isArray()) { elementDesc.setMinOccurs(particle.getIntMinOccurs()); elementDesc.setMaxOccurs(particle.getIntMaxOccurs()); //TODO axis seems to have the wrong name for this property based on how it is used elementDesc.setMaxOccursUnbounded(particle.getIntMaxOccurs() > 1); } fields[i] = elementDesc; } } } private static class TypeMappingLookup { private static final TypeMappingImpl SOAP_TM = DefaultSOAPEncodingTypeMappingImpl.getSingleton(); private static final TypeMappingImpl JAXRPC_TM = DefaultJAXRPC11TypeMappingImpl.getSingleton(); public static FactoryPair getFactoryPair(QName xmlType) { Class clazz = SOAP_TM.getClassForQName(xmlType, null, null); SerializerFactory sf; DeserializerFactory df; if (null != clazz) { sf = SOAP_TM.getSerializer(clazz, xmlType); df = SOAP_TM.getDeserializer(clazz, xmlType, null); } else { clazz = JAXRPC_TM.getClassForQName(xmlType, null, null); if (null == clazz) { return null; } sf = JAXRPC_TM.getSerializer(clazz, xmlType); df = JAXRPC_TM.getDeserializer(clazz, xmlType, null); } return new FactoryPair(sf.getClass(), df.getClass()); } } private static class FactoryPair { private final Class serializerFactoryClass; private final Class deserializerFactoryClass; private FactoryPair(Class serializerFactoryClass, Class deserializerFactoryClass) { this.serializerFactoryClass = serializerFactoryClass; this.deserializerFactoryClass = deserializerFactoryClass; } } }