/** * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.openejb.server.axis.assembler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.openejb.OpenEJBException; import org.apache.ws.commons.schema.XmlSchema; import org.apache.ws.commons.schema.XmlSchemaAttribute; import org.apache.ws.commons.schema.XmlSchemaChoice; import org.apache.ws.commons.schema.XmlSchemaCollection; import org.apache.ws.commons.schema.XmlSchemaComplexContentRestriction; import org.apache.ws.commons.schema.XmlSchemaComplexType; import org.apache.ws.commons.schema.XmlSchemaContent; import org.apache.ws.commons.schema.XmlSchemaElement; import org.apache.ws.commons.schema.XmlSchemaEnumerationFacet; import org.apache.ws.commons.schema.XmlSchemaGroupBase; import org.apache.ws.commons.schema.XmlSchemaObject; import org.apache.ws.commons.schema.XmlSchemaObjectCollection; import org.apache.ws.commons.schema.XmlSchemaParticle; import org.apache.ws.commons.schema.XmlSchemaSequence; import org.apache.ws.commons.schema.XmlSchemaSimpleType; import org.apache.ws.commons.schema.XmlSchemaSimpleTypeContent; import org.apache.ws.commons.schema.XmlSchemaSimpleTypeList; import org.apache.ws.commons.schema.XmlSchemaSimpleTypeRestriction; import org.apache.ws.commons.schema.XmlSchemaType; import org.apache.ws.commons.schema.XmlSchemaContentModel; import org.w3c.dom.Attr; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import javax.xml.namespace.QName; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.jar.JarFile; public class CommonsSchemaInfoBuilder { private static final Log log = LogFactory.getLog(CommonsSchemaInfoBuilder.class); private static final String XML_SCHEMA_NS = "http://www.w3.org/2001/XMLSchema"; private static final String XML_NS_NS = "http://www.w3.org/2000/xmlns/"; private static final String SOAP_ENCODING_NS = "http://schemas.xmlsoap.org/soap/encoding/"; private static final QName SOAP_ARRAY = new QName(SOAP_ENCODING_NS, "Array"); private static final QName SOAP_ARRAY_TYPE = new QName(SOAP_ENCODING_NS, "arrayType"); private static final QName WSDL_ARRAY_TYPE = new QName("http://schemas.xmlsoap.org/wsdl/", "arrayType"); private final XmlSchemaCollection xmlSchemaCollection; private final Map<QName, XmlTypeInfo> xmlTypes = new HashMap<QName, XmlTypeInfo>(); private final Map<QName, XmlElementInfo> xmlElements = new HashMap<QName, XmlElementInfo>(); public CommonsSchemaInfoBuilder(JarFile moduleFile, URI wsdlUri) throws OpenEJBException { if (moduleFile == null) throw new NullPointerException("moduleFile is null"); if (wsdlUri == null) throw new NullPointerException("wsdlUri is null"); CommonsSchemaLoader schemaLoader = new CommonsSchemaLoader(wsdlUri, moduleFile); xmlSchemaCollection = schemaLoader.loadSchema(); } public CommonsSchemaInfoBuilder(XmlSchemaCollection xmlSchemaCollection) { if (xmlSchemaCollection == null) throw new NullPointerException("schemaTypeSystem is null"); this.xmlSchemaCollection = xmlSchemaCollection; } public XmlSchemaInfo createSchemaInfo() throws OpenEJBException { buildXmlTypeInfos(); XmlSchemaInfo schemaInfo = new XmlSchemaInfo(); schemaInfo.types.putAll(xmlTypes); schemaInfo.elements.putAll(xmlElements); return schemaInfo; } private void buildXmlTypeInfos() { for (XmlSchema schema : xmlSchemaCollection.getXmlSchemas()) { // Global Elements for (Iterator iterator = schema.getElements().getValues(); iterator.hasNext(); ) { XmlSchemaElement globalElement = (XmlSchemaElement) iterator.next(); addGlobalElement(globalElement); } // Global Types for (Iterator iterator = schema.getSchemaTypes().getValues(); iterator.hasNext(); ) { XmlSchemaType globalType = (XmlSchemaType) iterator.next(); addType(globalType.getQName(), globalType); } } } private void addGlobalElement(XmlSchemaElement element) { // Nested anonymous type QName xmlType = element.getSchemaTypeName(); if (xmlType == null) { // Rule 1.b: Anonymous type inside an element ">E" xmlType = new QName(element.getQName().getNamespaceURI(), ">" + element.getQName().getLocalPart()); addType(xmlType, element.getSchemaType()); } // create the XmlElementInfo XmlElementInfo elementInfo = createXmlElementInfo(element.getQName(), xmlType, element); xmlElements.put(element.getQName(), elementInfo); } private static XmlElementInfo createXmlElementInfo(QName qname, QName xmlType, XmlSchemaElement element) { XmlElementInfo elementInfo = new XmlElementInfo(); elementInfo.qname = qname; elementInfo.xmlType = xmlType; elementInfo.minOccurs = element.getMinOccurs(); elementInfo.maxOccurs = element.getMaxOccurs(); elementInfo.nillable = element.isNillable(); return elementInfo; } private void addType(QName typeQName, XmlSchemaType type) { // skip built in xml schema types if (XML_SCHEMA_NS.equals(typeQName.getNamespaceURI())) { return; } XmlTypeInfo typeInfo = createXmlTypeInfo(typeQName, type); xmlTypes.put(typeQName, typeInfo); if (type instanceof XmlSchemaComplexType) { XmlSchemaComplexType complexType = (XmlSchemaComplexType) type; // process elements nested inside of this element List<XmlSchemaElement> elements = getNestedElements(complexType); for (XmlSchemaElement element : elements) { addNestedElement(element, typeInfo); } } } private void addNestedElement(XmlSchemaElement element, XmlTypeInfo enclosingType) { QName elementQName; QName typeQName; if (element.getRefName() == null) { // // Normal element in a type // // Element Name with namespace String elementNamespace = element.getQName().getNamespaceURI(); if (elementNamespace == null || elementNamespace.equals("")) { elementNamespace = enclosingType.qname.getNamespaceURI(); } elementQName = new QName(elementNamespace, element.getQName().getLocalPart()); // Type name if (element.getSchemaTypeName() != null) { // Global type typeQName = element.getSchemaTypeName(); } else { // Anonymous type, so we need to declare it // Rule 2.b: Anonymous element absolute name "T>N" String anonymoustName = enclosingType.qname.getLocalPart() + ">" + elementQName.getLocalPart(); QName anonymousQName = new QName(elementNamespace, anonymoustName); // Rule 1.b: Anonymous type name ">E" typeQName = new QName(elementNamespace, ">" + anonymousQName.getLocalPart()); addType(typeQName, element.getSchemaType()); } } else { // // Referenced global element // // Local the referenced global element XmlSchemaElement refElement = xmlSchemaCollection.getElementByQName(element.getRefName()); // The name and type of the nested element are determined by the referenced element elementQName = refElement.getQName(); typeQName = refElement.getSchemaTypeName(); } // Add element to enclosing type XmlElementInfo nestedElement = createXmlElementInfo(elementQName, typeQName, element); enclosingType.elements.put(nestedElement.qname, nestedElement); } public static XmlTypeInfo createXmlTypeInfo(QName qname, XmlSchemaType type) { if (qname == null) throw new NullPointerException("qname is null"); if (type == null) throw new NullPointerException("type is null"); XmlTypeInfo typeInfo = new XmlTypeInfo(); typeInfo.qname = qname; typeInfo.anonymous = qname.getLocalPart().indexOf('>') >= 0; if (type instanceof XmlSchemaSimpleType) { XmlSchemaSimpleType simpleType = (XmlSchemaSimpleType) type; XmlSchemaSimpleTypeContent content = simpleType.getContent(); if (content instanceof XmlSchemaSimpleTypeList) { XmlSchemaSimpleTypeList list = (XmlSchemaSimpleTypeList) content; typeInfo.simpleBaseType = list.getItemType().getQName(); // this is a list typeInfo.listType = true; } else if (content instanceof XmlSchemaSimpleTypeRestriction) { XmlSchemaSimpleTypeRestriction restriction = (XmlSchemaSimpleTypeRestriction) content; typeInfo.simpleBaseType = restriction.getBaseTypeName(); // is this an enumeration? for (Iterator iterator = restriction.getFacets().getIterator(); iterator.hasNext(); ) { if (iterator.next() instanceof XmlSchemaEnumerationFacet) { typeInfo.enumType = true; break; } } } } else if (type instanceof XmlSchemaComplexType) { XmlSchemaComplexType complexType = (XmlSchemaComplexType) type; // SOAP array component type typeInfo.arrayComponentType = extractSoapArrayComponentType(complexType); // process attributes (skip soap arrays which have non-mappable attributes) if (!isSoapArray(complexType)) { XmlSchemaObjectCollection attributes = complexType.getAttributes(); for (Iterator iterator = attributes.getIterator(); iterator.hasNext(); ) { Object item = iterator.next(); if (item instanceof XmlSchemaAttribute) { XmlSchemaAttribute attribute = (XmlSchemaAttribute) item; Object old = typeInfo.attributes.put(attribute.getQName().getLocalPart(), attribute.getSchemaTypeName()); if (old != null) { throw new IllegalArgumentException("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); } } } } } else { log.warn("Unknown schema type class " + typeInfo.getClass().getName()); } return typeInfo; } private static boolean isSoapArray(XmlSchemaComplexType complexType) { // Soap arrays are based on complex content restriction XmlSchemaContentModel contentModel = complexType.getContentModel(); if (contentModel == null) { return false; } XmlSchemaContent content = contentModel.getContent(); if (!(content instanceof XmlSchemaComplexContentRestriction)) { return false; } XmlSchemaComplexContentRestriction restriction = (XmlSchemaComplexContentRestriction) content; return SOAP_ARRAY.equals(restriction.getBaseTypeName()); } /** * Extract the nested component type of an Array from the XML Schema Type. * * @return the QName of the nested component type or null if the schema type can not be determined * @throws org.apache.openejb.OpenEJBException if the XML Schema Type can not represent an Array @param complexType */ private static QName extractSoapArrayComponentType(XmlSchemaComplexType complexType) { // Soap arrays are based on complex content restriction if (!isSoapArray(complexType)) { return null; } XmlSchemaComplexContentRestriction restriction = (XmlSchemaComplexContentRestriction) complexType.getContentModel().getContent(); //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> XmlSchemaObjectCollection attributes = restriction.getAttributes(); for (Iterator iterator = attributes.getIterator(); iterator.hasNext(); ) { Object item = iterator.next(); if (item instanceof XmlSchemaAttribute) { XmlSchemaAttribute attribute = (XmlSchemaAttribute) item; if (attribute.getRefName().equals(SOAP_ARRAY_TYPE)) { for (Attr attr : attribute.getUnhandledAttributes()) { QName attQName = new QName(attr.getNamespaceURI(), attr.getLocalName()); if (WSDL_ARRAY_TYPE.equals(attQName)) { // value is a namespace prefixed xsd type String value = attr.getValue(); // extract local part int pos = value.lastIndexOf(":"); QName componentType; if (pos < 0) { componentType = new QName("", value); } else { String localPart = value.substring(pos + 1); // resolve the namespace prefix String prefix = value.substring(0, pos); String namespace = getNamespaceForPrefix(prefix, attr.getOwnerElement()); componentType = new QName(namespace, localPart); } log.debug("determined component type from element type"); return componentType; } } } } } // 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> XmlSchemaParticle particle = restriction.getParticle(); if (particle instanceof XmlSchemaSequence) { XmlSchemaSequence sequence = (XmlSchemaSequence) particle; if (sequence.getItems().getCount() != 1) { throw new IllegalArgumentException("more than one element inside array definition: " + complexType); } XmlSchemaObject item = sequence.getItems().getItem(0); if (item instanceof XmlSchemaElement) { XmlSchemaElement element = (XmlSchemaElement) item; QName componentType = element.getSchemaTypeName(); log.debug("determined component type from element type"); return componentType; } } return null; } private static String getNamespaceForPrefix(String prefix, Element element) { NamedNodeMap attributes = element.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { Node node = attributes.item(i); if (node instanceof Attr) { Attr attr = (Attr) node; if (XML_NS_NS.equals(attr.getNamespaceURI())) { // this is a namespace declaration, is it the one we are looking for? if (attr.getLocalName().equals(prefix)) { return attr.getValue(); } } } } // try parent if (element.getParentNode() instanceof Element) { return getNamespaceForPrefix(prefix, (Element) element.getParentNode()); } // didn't find it - just use prefix as the namespace return prefix; } private static List<XmlSchemaElement> getNestedElements(XmlSchemaComplexType complexType) { List<XmlSchemaElement> elements = new ArrayList<XmlSchemaElement>(); XmlSchemaParticle particle = complexType.getParticle(); if (particle instanceof XmlSchemaElement) { XmlSchemaElement element = (XmlSchemaElement) particle; elements.add(element); } else if (particle instanceof XmlSchemaGroupBase && !(particle instanceof XmlSchemaChoice)) { XmlSchemaGroupBase groupBase = (XmlSchemaGroupBase) particle; for (Iterator iterator = groupBase.getItems().getIterator(); iterator.hasNext(); ) { XmlSchemaParticle child = (XmlSchemaParticle) iterator.next(); if (child instanceof XmlSchemaElement) { XmlSchemaElement element = (XmlSchemaElement) child; elements.add(element); } } } else { // ignore all other types... you can have these other types, but JAX-RPC doesn't support them } return elements; } }