blob: d85596dcf222323b2d1599fbd8bfd565bec011cb [file] [log] [blame]
/**
*
* 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.openejb.jee.JavaWsdlMapping;
import org.apache.openejb.jee.JavaXmlTypeMapping;
import org.apache.openejb.jee.VariableMapping;
import static org.apache.openejb.server.axis.assembler.JaxRpcTypeInfo.SerializerType;
import javax.xml.namespace.QName;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class HeavyweightTypeInfoBuilder {
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 JavaWsdlMapping mapping;
private final ClassLoader classLoader;
private final XmlSchemaInfo schemaInfo;
private final Set wrapperElementQNames;
private final Collection<JaxRpcOperationInfo> operations;
private final boolean hasEncoded;
public HeavyweightTypeInfoBuilder(JavaWsdlMapping mapping, XmlSchemaInfo schemaInfo, ClassLoader classLoader, Set wrapperElementQNames, Collection<JaxRpcOperationInfo> operations, boolean hasEncoded) {
this.mapping = mapping;
this.classLoader = classLoader;
this.schemaInfo = schemaInfo;
this.wrapperElementQNames = wrapperElementQNames;
this.operations = operations;
this.hasEncoded = hasEncoded;
}
public List<JaxRpcTypeInfo> buildTypeInfo() throws OpenEJBException {
List<JaxRpcTypeInfo> typeInfos = new ArrayList<>();
Set<QName> mappedTypeQNames = new HashSet<>();
//
// Map types with explicity Java to XML mappings
//
for (JavaXmlTypeMapping javaXmlTypeMapping : mapping.getJavaXmlTypeMapping()) {
// get the QName for this mapping
QName qname;
if (javaXmlTypeMapping.getRootTypeQname() != null) {
qname = javaXmlTypeMapping.getRootTypeQname();
// Skip the wrapper elements.
if (wrapperElementQNames.contains(qname)) {
continue;
}
} else if (javaXmlTypeMapping.getAnonymousTypeQname() != null) {
String anonTypeQNameString = javaXmlTypeMapping.getAnonymousTypeQname();
// this appears to be ignored...
int pos = anonTypeQNameString.lastIndexOf(":");
if (pos == -1) {
throw new OpenEJBException("anon QName is invalid, no final ':' " + anonTypeQNameString);
}
String namespace = anonTypeQNameString.substring(0, pos);
String localPart = anonTypeQNameString.substring(pos + 1);
qname = new QName(namespace, localPart);
// Skip the wrapper elements.
// todo why is this +2
if (wrapperElementQNames.contains(new QName(namespace, anonTypeQNameString.substring(pos + 2)))) {
continue;
}
} else {
throw new OpenEJBException("either root type qname or anonymous type qname must be set");
}
// get the xml type qname of this mapping
QName xmlTypeQName;
if ("element".equals(javaXmlTypeMapping.getQNameScope())) {
XmlElementInfo elementInfo = schemaInfo.elements.get(qname);
if (elementInfo == null) {
LOG.warn("Element [" + qname + "] not been found in schema, known elements: " + schemaInfo.elements.keySet());
}
xmlTypeQName = elementInfo.xmlType;
} else {
xmlTypeQName = qname;
}
// finally, get the xml type info for the mapping
XmlTypeInfo xmlTypeInfo = schemaInfo.types.get(xmlTypeQName);
if (xmlTypeInfo == null) {
// if this is a built in type then assume this is a redundant mapping
if (WEBSERVICE_NAMESPACES.contains(xmlTypeInfo.qname.getNamespaceURI())) {
continue;
}
LOG.warn("Schema type QName [" + qname + "] not been found in schema: " + schemaInfo.types.keySet());
continue;
}
// mark this type as mapped
mappedTypeQNames.add(xmlTypeInfo.qname);
// load the java class
Class clazz;
try {
clazz = Class.forName(javaXmlTypeMapping.getJavaType(), false, classLoader);
} catch (ClassNotFoundException e) {
throw new OpenEJBException("Could not load java type " + javaXmlTypeMapping.getJavaType(), e);
}
// create the jax-rpc type mapping
JaxRpcTypeInfo typeInfo = createTypeInfo(qname, xmlTypeInfo, clazz);
mapFields(clazz, xmlTypeInfo, javaXmlTypeMapping, typeInfo);
typeInfos.add(typeInfo);
}
//
// Map types used in operations
//
for (JaxRpcOperationInfo operationInfo : operations) {
List<JaxRpcParameterInfo> parameters = new ArrayList<>(operationInfo.parameters);
// add the return type to the parameters so it is processed below
if (operationInfo.returnXmlType != null) {
JaxRpcParameterInfo returnParameter = new JaxRpcParameterInfo();
returnParameter.xmlType = operationInfo.returnXmlType;
returnParameter.javaType = operationInfo.returnJavaType;
parameters.add(returnParameter);
}
// add type mappings for each parameter (including the return type)
for (JaxRpcParameterInfo parameterInfo : parameters) {
QName xmlType = parameterInfo.xmlType;
// skip types that have already been mapped or are built in types
if (xmlType == null ||
mappedTypeQNames.contains(xmlType) ||
xmlType.getNamespaceURI().equals(XML_SCHEMA_NS) ||
xmlType.getNamespaceURI().equals(SOAP_ENCODING_NS)) {
continue;
}
// get the xml type info
XmlTypeInfo xmlTypeInfo = schemaInfo.types.get(xmlType);
if (xmlTypeInfo == null) {
LOG.warn("Type QName [" + xmlType + "] defined by operation [" + operationInfo + "] has not been found in schema: " + schemaInfo.types.keySet());
continue;
}
mappedTypeQNames.add(xmlTypeInfo.qname);
// load the java class
Class<?> clazz;
try {
clazz = classLoader.loadClass(parameterInfo.javaType);
} catch (ClassNotFoundException e) {
throw new OpenEJBException("Could not load paramter");
}
// we only process simpleTypes and arrays (not normal complex types)
if (xmlTypeInfo.simpleBaseType == null && !clazz.isArray()) {
if (!mappedTypeQNames.contains(xmlTypeInfo.qname)) {
// TODO: this lookup is not enough: the jaxrpc mapping file may define an anonymous mapping
LOG.warn("Operation " + operationInfo.name + "] uses XML type [" + xmlTypeInfo + "], whose mapping is not declared by the jaxrpc mapping file.\n Continuing deployment; " + "yet, the deployment is not-portable.");
}
continue;
}
// create the jax-rpc type mapping
JaxRpcTypeInfo typeInfo = createTypeInfo(parameterInfo.qname, xmlTypeInfo, clazz);
typeInfos.add(typeInfo);
}
}
return typeInfos;
}
/**
* Creates a JaxRpcTypeInfo based on the information contained in the XML Schema Type and Java Class.
*
* @param xmlTypeInfo the xml schema for the type
* @param clazz the java class for the type
* @return the JaxRpcTypeInfo object
* @throws OpenEJBException if the schema is invalid
*/
private JaxRpcTypeInfo createTypeInfo(QName qname, XmlTypeInfo xmlTypeInfo, Class clazz) throws OpenEJBException {
SerializerType serializerType;
if (xmlTypeInfo.listType) {
serializerType = SerializerType.LIST;
} else if (clazz.isArray()) {
serializerType = SerializerType.ARRAY;
} else if (xmlTypeInfo.enumType) {
serializerType = SerializerType.ENUM;
} else {
serializerType = SerializerType.OTHER;
}
JaxRpcTypeInfo typeInfo = new JaxRpcTypeInfo();
typeInfo.qname = qname;
typeInfo.javaType = clazz.getName();
typeInfo.serializerType = serializerType;
typeInfo.simpleBaseType = xmlTypeInfo.simpleBaseType;
// If we understand the axis comments correctly, componentQName is never set for a webservice.
if (serializerType == SerializerType.ARRAY) {
typeInfo.componentType = xmlTypeInfo.arrayComponentType;
}
return typeInfo;
}
/**
* Map the (nested) fields of a XML Schema Type to Java Beans properties or public fields of the specified Java Class.
*
* @param javaClass the java class to map
* @param xmlTypeInfo the xml schema for the type
* @param javaXmlTypeMapping the java to xml type mapping metadata
* @param typeInfo the JaxRpcTypeInfo for this type
* @throws OpenEJBException if the XML Schema Type can not be mapped to the Java Class
*/
private void mapFields(Class javaClass, XmlTypeInfo xmlTypeInfo, JavaXmlTypeMapping javaXmlTypeMapping, JaxRpcTypeInfo typeInfo) throws OpenEJBException {
// Skip arrays since they can't define a variable-mapping element
if (!javaClass.isArray()) {
// if there is a variable-mapping, LOG a warning
if (!javaXmlTypeMapping.getVariableMapping().isEmpty()) {
LOG.warn("Ignoring variable-mapping defined for class " + javaClass + " which is an array.");
}
return;
}
// Index Java bean properties by name
Map<String, Class> properties = new HashMap<>();
try {
PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(javaClass).getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
properties.put(propertyDescriptor.getName(), propertyDescriptor.getPropertyType());
}
} catch (IntrospectionException e) {
throw new OpenEJBException("Class " + javaClass + " is not a valid javabean", e);
}
for (VariableMapping variableMapping : javaXmlTypeMapping.getVariableMapping()) {
String fieldName = variableMapping.getJavaVariableName();
if (variableMapping.getXmlAttributeName() != null) {
JaxRpcFieldInfo fieldInfo = new JaxRpcFieldInfo();
fieldInfo.name = fieldName;
// verify that the property exists on the java class
Class javaType = properties.get(fieldName);
if (javaType == null) {
throw new OpenEJBException("field name " + fieldName + " not found in " + properties);
}
String attributeLocalName = variableMapping.getXmlAttributeName();
QName xmlName = new QName("", attributeLocalName);
fieldInfo.xmlName = xmlName;
fieldInfo.xmlType = xmlTypeInfo.attributes.get(attributeLocalName);
if (fieldInfo.xmlType == null) {
throw new OpenEJBException("attribute " + xmlName + " not found in schema " + xmlTypeInfo.qname);
}
typeInfo.fields.add(fieldInfo);
} else {
JaxRpcFieldInfo fieldInfo = new JaxRpcFieldInfo();
fieldInfo.isElement = true;
fieldInfo.name = fieldName;
// verify that the property exists on the java class or there is a public field
Class javaType = 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 OpenEJBException("field name " + fieldName + " not found in " + properties, e);
}
}
QName xmlName = new QName("", variableMapping.getXmlElementName());
XmlElementInfo nestedElement = xmlTypeInfo.elements.get(xmlName);
if (nestedElement == null) {
String ns = xmlTypeInfo.qname.getNamespaceURI();
xmlName = new QName(ns, variableMapping.getXmlElementName());
nestedElement = xmlTypeInfo.elements.get(xmlName);
if (nestedElement == null) {
throw new OpenEJBException("element " + xmlName + " not found in schema " + xmlTypeInfo.qname);
}
}
fieldInfo.isNillable = nestedElement.nillable || hasEncoded;
fieldInfo.xmlName = xmlName;
// xml type
if (nestedElement.xmlType != null) {
fieldInfo.xmlType = nestedElement.xmlType;
} else {
QName anonymousName;
if (xmlTypeInfo.anonymous) {
anonymousName = new QName(xmlTypeInfo.qname.getNamespaceURI(), xmlTypeInfo.qname.getLocalPart() +
">" + nestedElement.qname.getLocalPart());
} else {
anonymousName = new QName(xmlTypeInfo.qname.getNamespaceURI(),
">" + xmlTypeInfo.qname.getLocalPart() + ">" + nestedElement.qname.getLocalPart());
}
fieldInfo.xmlType = anonymousName;
}
if (javaType.isArray()) {
fieldInfo.minOccurs = nestedElement.minOccurs;
fieldInfo.maxOccurs = nestedElement.maxOccurs;
}
typeInfo.fields.add(fieldInfo);
}
}
}
/**
* All of the known built in XML Schemas used by webservices. This is used to supress unknown type exceptions
*/
private static final Set<String> WEBSERVICE_NAMESPACES = Collections.unmodifiableSet(new LinkedHashSet<String>(Arrays.asList(
"http://schemas.xmlsoap.org/soap/encoding/", // SOAP 1.1
"http://www.w3.org/2003/05/soap-encoding", // SOAP 1.2
"http://xml.apache.org/xml-soap", // Apache XMLSOAP
"http://www.w3.org/1999/XMLSchema", // XSD 1999
"http://www.w3.org/2000/10/XMLSchema", // XSD 2000
"http://www.w3.org/2001/XMLSchema", // XSD 2001
"http://www.w3.org/XML/1998/namespace" // XML (for xml-any)
)));
}