001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.geronimo.axis.builder;
018
019 import java.beans.IntrospectionException;
020 import java.beans.Introspector;
021 import java.beans.PropertyDescriptor;
022 import java.lang.reflect.Field;
023 import java.util.ArrayList;
024 import java.util.Collection;
025 import java.util.HashMap;
026 import java.util.HashSet;
027 import java.util.Iterator;
028 import java.util.List;
029 import java.util.Map;
030 import java.util.Set;
031 import javax.xml.namespace.QName;
032 import javax.xml.rpc.encoding.DeserializerFactory;
033 import javax.xml.rpc.encoding.SerializerFactory;
034
035 import org.apache.axis.description.AttributeDesc;
036 import org.apache.axis.description.ElementDesc;
037 import org.apache.axis.description.FieldDesc;
038 import org.apache.axis.description.OperationDesc;
039 import org.apache.axis.description.ParameterDesc;
040 import org.apache.axis.encoding.DefaultJAXRPC11TypeMappingImpl;
041 import org.apache.axis.encoding.DefaultSOAPEncodingTypeMappingImpl;
042 import org.apache.axis.encoding.TypeMappingImpl;
043 import org.apache.axis.encoding.XMLType;
044 import org.apache.axis.encoding.ser.ArrayDeserializerFactory;
045 import org.apache.axis.encoding.ser.ArraySerializerFactory;
046 import org.apache.axis.encoding.ser.BeanDeserializerFactory;
047 import org.apache.axis.encoding.ser.BeanSerializerFactory;
048 import org.apache.axis.encoding.ser.EnumDeserializerFactory;
049 import org.apache.axis.encoding.ser.EnumSerializerFactory;
050 import org.apache.axis.encoding.ser.SimpleListDeserializerFactory;
051 import org.apache.axis.encoding.ser.SimpleListSerializerFactory;
052 import org.apache.commons.logging.Log;
053 import org.apache.commons.logging.LogFactory;
054 import org.apache.geronimo.axis.client.ArrayTypeInfo;
055 import org.apache.geronimo.axis.client.TypeInfo;
056 import org.apache.geronimo.common.DeploymentException;
057 import org.apache.geronimo.kernel.ClassLoading;
058 import org.apache.geronimo.xbeans.j2ee.JavaWsdlMappingType;
059 import org.apache.geronimo.xbeans.j2ee.JavaXmlTypeMappingType;
060 import org.apache.geronimo.xbeans.j2ee.VariableMappingType;
061 import org.apache.geronimo.webservices.builder.SchemaTypeKey;
062 import org.apache.xmlbeans.SchemaLocalAttribute;
063 import org.apache.xmlbeans.SchemaParticle;
064 import org.apache.xmlbeans.SchemaProperty;
065 import org.apache.xmlbeans.SchemaType;
066 import org.apache.xmlbeans.soap.SOAPArrayType;
067 import org.apache.xmlbeans.soap.SchemaWSDLArrayType;
068
069 /**
070 * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
071 */
072 public class HeavyweightTypeInfoBuilder implements TypeInfoBuilder {
073 private static final String SOAP_ENCODING_NS = "http://schemas.xmlsoap.org/soap/encoding/";
074 private static final String XML_SCHEMA_NS = "http://www.w3.org/2001/XMLSchema";
075
076 private static final Log log = LogFactory.getLog(HeavyweightTypeInfoBuilder.class);
077
078 private final ClassLoader cl;
079 private final Map schemaTypeKeyToSchemaTypeMap;
080 private final Set wrapperElementQNames;
081 private final Collection operations;
082 private final boolean hasEncoded;
083
084 public HeavyweightTypeInfoBuilder(ClassLoader cl, Map schemaTypeKeyToSchemaTypeMap, Set wrapperElementQNames, Collection operations, boolean hasEncoded) {
085 this.cl = cl;
086 this.schemaTypeKeyToSchemaTypeMap = schemaTypeKeyToSchemaTypeMap;
087 this.wrapperElementQNames = wrapperElementQNames;
088 this.operations = operations;
089 this.hasEncoded = hasEncoded;
090 }
091
092 public List buildTypeInfo(JavaWsdlMappingType mapping) throws DeploymentException {
093 List typeInfoList = new ArrayList();
094
095 Set mappedTypeQNames = new HashSet();
096
097 JavaXmlTypeMappingType[] javaXmlTypeMappings = mapping.getJavaXmlTypeMappingArray();
098 for (int j = 0; j < javaXmlTypeMappings.length; j++) {
099 JavaXmlTypeMappingType javaXmlTypeMapping = javaXmlTypeMappings[j];
100
101 SchemaTypeKey key;
102 boolean isElement = javaXmlTypeMapping.getQnameScope().getStringValue().equals("element");
103 boolean isSimpleType = javaXmlTypeMapping.getQnameScope().getStringValue().equals("simpleType");
104 if (javaXmlTypeMapping.isSetRootTypeQname()) {
105 QName typeQName = javaXmlTypeMapping.getRootTypeQname().getQNameValue();
106 key = new SchemaTypeKey(typeQName, isElement, isSimpleType, false, null);
107
108 // Skip the wrapper elements.
109 if (wrapperElementQNames.contains(typeQName)) {
110 continue;
111 }
112 } else if (javaXmlTypeMapping.isSetAnonymousTypeQname()) {
113 String anonTypeQNameString = javaXmlTypeMapping.getAnonymousTypeQname().getStringValue();
114 int pos = anonTypeQNameString.lastIndexOf(":");
115 if (pos == -1) {
116 throw new DeploymentException("anon QName is invalid, no final ':' " + anonTypeQNameString);
117 }
118
119 //this appears to be ignored...
120 QName typeQName = new QName(anonTypeQNameString.substring(0, pos), anonTypeQNameString.substring(pos + 1));
121 key = new SchemaTypeKey(typeQName, isElement, isSimpleType, true, null);
122
123 // Skip the wrapper elements.
124 if (wrapperElementQNames.contains(new QName(anonTypeQNameString.substring(0, pos), anonTypeQNameString.substring(pos + 2)))) {
125 continue;
126 }
127 } else {
128 throw new DeploymentException("either root type qname or anonymous type qname must be set");
129 }
130
131 SchemaType schemaType = (SchemaType) schemaTypeKeyToSchemaTypeMap.get(key);
132 if (schemaType == null) {
133 // if it is a built-in type, then one assumes a redundant mapping.
134 if (null != TypeMappingLookup.getFactoryPair(key.getqName())) {
135 continue;
136 }
137 // throw new DeploymentException("Schema type key " + key + " not found in analyzed schema: " + schemaTypeKeyToSchemaTypeMap);
138 log.warn("Schema type key " + key + " not found in analyzed schema: " + schemaTypeKeyToSchemaTypeMap);
139 continue;
140 }
141 mappedTypeQNames.add(key.getqName());
142
143 String className = javaXmlTypeMapping.getJavaType().getStringValue().trim();
144 Class clazz = null;
145 try {
146 clazz = ClassLoading.loadClass(className, cl);
147 } catch (ClassNotFoundException e2) {
148 throw new DeploymentException("Could not load java type", e2);
149 }
150
151 TypeInfo.UpdatableTypeInfo internalTypeInfo = defineSerializerPair(schemaType, clazz);
152
153 populateInternalTypeInfo(clazz, key, schemaType, javaXmlTypeMapping, internalTypeInfo);
154
155 typeInfoList.add(internalTypeInfo.buildTypeInfo());
156 }
157
158 Map qNameToKey = new HashMap();
159 for (Iterator iter = schemaTypeKeyToSchemaTypeMap.keySet().iterator(); iter.hasNext();) {
160 SchemaTypeKey key = (SchemaTypeKey) iter.next();
161 qNameToKey.put(key.getqName(), key);
162 }
163
164 for (Iterator iter = operations.iterator(); iter.hasNext();) {
165 OperationDesc operationDesc = (OperationDesc) iter.next();
166 ArrayList parameters = new ArrayList(operationDesc.getParameters());
167 ParameterDesc returnParameterDesc = operationDesc.getReturnParamDesc();
168 if (null != returnParameterDesc.getTypeQName() &&
169 false == returnParameterDesc.getTypeQName().equals(XMLType.AXIS_VOID)) {
170 parameters.add(returnParameterDesc);
171 }
172 for (Iterator iterator = parameters.iterator(); iterator.hasNext();) {
173 ParameterDesc parameterDesc = (ParameterDesc) iterator.next();
174 QName typeQName = parameterDesc.getTypeQName();
175 if (null == typeQName) {
176 continue;
177 } else if (mappedTypeQNames.contains(typeQName)) {
178 continue;
179 } else if (typeQName.getNamespaceURI().equals(XML_SCHEMA_NS) ||
180 typeQName.getNamespaceURI().equals(SOAP_ENCODING_NS)) {
181 continue;
182 }
183
184 SchemaTypeKey key = (SchemaTypeKey) qNameToKey.get(typeQName);
185 if (null == key) {
186 log.warn("Type QName [" + typeQName + "] defined by operation [" +
187 operationDesc + "] has not been found in schema: " + schemaTypeKeyToSchemaTypeMap);
188 continue;
189 }
190 SchemaType schemaType = (SchemaType) schemaTypeKeyToSchemaTypeMap.get(key);
191 mappedTypeQNames.add(key.getqName());
192
193 if (false == schemaType.isSimpleType()) {
194 if (false == parameterDesc.getJavaType().isArray()) {
195 if (false == mappedTypeQNames.contains(schemaType.getName())) {
196 // TODO: this lookup is not enough: the jaxrpc mapping file may define an anonymous
197 // mapping.
198 log.warn("Operation [" + operationDesc + "] uses XML type [" + schemaType +
199 "], whose mapping is not declared by the jaxrpc mapping file.\n Continuing deployment; " +
200 "yet, the deployment is not-portable.");
201 }
202 continue;
203 }
204 }
205
206 Class clazz = parameterDesc.getJavaType();
207 TypeInfo.UpdatableTypeInfo internalTypeInfo = defineSerializerPair(schemaType, clazz);
208 setTypeQName(internalTypeInfo, key);
209 internalTypeInfo.setFields(new FieldDesc[0]);
210
211 typeInfoList.add(internalTypeInfo.buildTypeInfo());
212 }
213 }
214
215 return typeInfoList;
216 }
217
218 private TypeInfo.UpdatableTypeInfo defineSerializerPair(SchemaType schemaType, Class clazz)
219 throws DeploymentException {
220 TypeInfo.UpdatableTypeInfo internalTypeInfo = new TypeInfo.UpdatableTypeInfo();
221 Class serializerFactoryClass = null;
222 Class deserializerFactoryClass = null;
223 if (schemaType.isSimpleType()) {
224 if (SchemaType.ATOMIC == schemaType.getSimpleVariety()) {
225 if (clazz.isArray()) {
226 internalTypeInfo = new ArrayTypeInfo.UpdatableArrayTypeInfo();
227 serializerFactoryClass = ArraySerializerFactory.class;
228 deserializerFactoryClass = ArrayDeserializerFactory.class;
229 //TODO set componentType, componentQName
230 } else if (null != schemaType.getEnumerationValues()) {
231 serializerFactoryClass = EnumSerializerFactory.class;
232 deserializerFactoryClass = EnumDeserializerFactory.class;
233 } else {
234 QName typeQName = schemaType.getPrimitiveType().getName();
235 FactoryPair pair = (FactoryPair) TypeMappingLookup.getFactoryPair(typeQName);
236 if (null == pair) {
237 throw new DeploymentException("Primitive type [" + typeQName + "] is not registered.");
238 }
239 serializerFactoryClass = pair.serializerFactoryClass;
240 deserializerFactoryClass = pair.deserializerFactoryClass;
241 }
242 } else if (SchemaType.LIST == schemaType.getSimpleVariety()) {
243 serializerFactoryClass = SimpleListSerializerFactory.class;
244 deserializerFactoryClass = SimpleListDeserializerFactory.class;
245 } else {
246 throw new DeploymentException("Schema type [" + schemaType + "] is invalid.");
247 }
248 } else {
249 if (clazz.isArray()) {
250 internalTypeInfo = new ArrayTypeInfo.UpdatableArrayTypeInfo();
251 serializerFactoryClass = ArraySerializerFactory.class;
252 deserializerFactoryClass = ArrayDeserializerFactory.class;
253 QName componentType = null;
254 //First, handle case that looks like this:
255 // <complexType name="ArrayOfstring">
256 // <complexContent>
257 // <restriction base="soapenc:Array">
258 // <attribute ref="soapenc:arrayType" wsdl:arrayType="xsd:string[]"/>
259 // </restriction>
260 // </complexContent>
261 // </complexType>
262 SchemaLocalAttribute arrayTypeAttribute = schemaType.getAttributeModel().getAttribute(new QName(SOAP_ENCODING_NS, "arrayType"));
263 if (arrayTypeAttribute != null) {
264 SchemaWSDLArrayType wsdlArrayType = (SchemaWSDLArrayType) arrayTypeAttribute;
265 SOAPArrayType soapArrayType = wsdlArrayType.getWSDLArrayType();
266 if (soapArrayType != null) {
267 componentType = soapArrayType.getQName();
268 log.debug("extracted componentType " + componentType + " from schemaType " + schemaType);
269 } else {
270 log.info("no SOAPArrayType for component from schemaType " + schemaType);
271 }
272 } else {
273 log.warn("No soap array info for schematype: " + schemaType);
274 }
275 if (componentType == null) {
276 //If that didn't work, try to handle case like this:
277 // <complexType name="ArrayOfstring1">
278 // <complexContent>
279 // <restriction base="soapenc:Array">
280 // <sequence>
281 // <element name="string1" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
282 // </sequence>
283 // </restriction>
284 // </complexContent>
285 // </complexType>
286 //todo consider if we should check for maxOccurs > 1
287 if (schemaType.getBaseType().getName().equals(new QName(SOAP_ENCODING_NS, "Array"))) {
288 SchemaProperty[] properties = schemaType.getDerivedProperties();
289 if (properties.length != 1) {
290 throw new DeploymentException("more than one element inside array definition: " + schemaType);
291 }
292 componentType = properties[0].getType().getName();
293 log.debug("determined component type from element type");
294 }
295
296 }
297
298 ((ArrayTypeInfo.UpdatableArrayTypeInfo)internalTypeInfo).setComponentType(componentType);
299 //If we understand the axis comments correctly, componentQName is never set for j2ee ws.
300 } else {
301 QName typeQName;
302 if (SchemaType.SIMPLE_CONTENT == schemaType.getContentType()) {
303 typeQName = schemaType.getBaseType().getName();
304 } else if (SchemaType.EMPTY_CONTENT == schemaType.getContentType() ||
305 SchemaType.ELEMENT_CONTENT == schemaType.getContentType() ||
306 SchemaType.MIXED_CONTENT == schemaType.getContentType()) {
307 typeQName = schemaType.getName();
308 } else {
309 throw new DeploymentException("Schema type [" + schemaType + "] is invalid.");
310 }
311 FactoryPair pair = (FactoryPair) TypeMappingLookup.getFactoryPair(typeQName);
312 if (null != pair) {
313 serializerFactoryClass = pair.serializerFactoryClass;
314 deserializerFactoryClass = pair.deserializerFactoryClass;
315 } else {
316 serializerFactoryClass = BeanSerializerFactory.class;
317 deserializerFactoryClass = BeanDeserializerFactory.class;
318 }
319 }
320 }
321
322 internalTypeInfo.setClazz(clazz);
323 internalTypeInfo.setSerializerClass(serializerFactoryClass);
324 internalTypeInfo.setDeserializerClass(deserializerFactoryClass);
325 return internalTypeInfo;
326 }
327
328 private void setTypeQName(TypeInfo.UpdatableTypeInfo typeInfo, SchemaTypeKey key) {
329 //figure out the name axis expects to look up under.
330 QName axisKey = key.getElementQName();
331 if (axisKey == null) {
332 axisKey = key.getqName();
333 }
334 typeInfo.setQName(axisKey);
335 }
336
337 private void populateInternalTypeInfo(Class javaClass, SchemaTypeKey key, SchemaType schemaType, JavaXmlTypeMappingType javaXmlTypeMapping, TypeInfo.UpdatableTypeInfo typeInfo) throws DeploymentException {
338 String ns = key.getqName().getNamespaceURI();
339 typeInfo.setCanSearchParents(schemaType.getDerivationType() == SchemaType.DT_RESTRICTION);
340
341 setTypeQName(typeInfo, key);
342
343 Map paramNameToType = new HashMap();
344 if (null == schemaType.getContentModel()) {
345 ;
346 } else if (SchemaParticle.SEQUENCE == schemaType.getContentModel().getParticleType()
347 || SchemaParticle.ALL == schemaType.getContentModel().getParticleType()) {
348 SchemaParticle[] properties = schemaType.getContentModel().getParticleChildren();
349 for (int i = 0; i < properties.length; i++) {
350 SchemaParticle parameter = properties[i];
351 paramNameToType.put(parameter.getName(), parameter);
352 }
353 } else if (SchemaParticle.ELEMENT == schemaType.getContentModel().getParticleType()) {
354 SchemaParticle parameter = schemaType.getContentModel();
355 paramNameToType.put(parameter.getName(), parameter);
356 } else {
357 throw new DeploymentException("Only element, sequence, and all particle types are supported." +
358 " SchemaType name =" + schemaType.getName());
359 }
360
361 Map attNameToType = new HashMap();
362 if (null != schemaType.getAttributeModel()) {
363 SchemaLocalAttribute[] attributes = schemaType.getAttributeModel().getAttributes();
364 for (int i = 0; i < attributes.length; i++) {
365 SchemaLocalAttribute attribute = attributes[i];
366 Object old = attNameToType.put(attribute.getName().getLocalPart(), attribute);
367 if (old != null) {
368 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);
369 }
370 }
371 }
372
373 VariableMappingType[] variableMappings = javaXmlTypeMapping.getVariableMappingArray();
374
375 // short-circuit the processing of arrays as they should not define variable-mapping elements.
376 if (javaClass.isArray()) {
377 if (0 != variableMappings.length) {
378 // for portability reason we simply warn and not fail.
379 log.warn("Ignoring variable-mapping defined for class " + javaClass + " which is an array.");
380 }
381 typeInfo.setFields(new FieldDesc[0]);
382 return;
383 }
384
385 FieldDesc[] fields = new FieldDesc[variableMappings.length];
386 typeInfo.setFields(fields);
387
388 PropertyDescriptor[] propertyDescriptors = new PropertyDescriptor[0];
389 try {
390 propertyDescriptors = Introspector.getBeanInfo(javaClass).getPropertyDescriptors();
391 } catch (IntrospectionException e) {
392 throw new DeploymentException("Class " + javaClass + " is not a valid javabean", e);
393 }
394 Map properties = new HashMap();
395 for (int i = 0; i < propertyDescriptors.length; i++) {
396 PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
397 properties.put(propertyDescriptor.getName(), propertyDescriptor.getPropertyType());
398 }
399 for (int i = 0; i < variableMappings.length; i++) {
400 VariableMappingType variableMapping = variableMappings[i];
401 String fieldName = variableMapping.getJavaVariableName().getStringValue().trim();
402
403 if (variableMapping.isSetXmlAttributeName()) {
404 AttributeDesc attributeDesc = new AttributeDesc();
405 attributeDesc.setFieldName(fieldName);
406 Class javaType = (Class) properties.get(fieldName);
407 if (javaType == null) {
408 throw new DeploymentException("field name " + fieldName + " not found in " + properties);
409 }
410 String attributeLocalName = variableMapping.getXmlAttributeName().getStringValue().trim();
411 QName xmlName = new QName("", attributeLocalName);
412 attributeDesc.setXmlName(xmlName);
413
414 SchemaLocalAttribute attribute = (SchemaLocalAttribute) attNameToType.get(attributeLocalName);
415 if (null == attribute) {
416 throw new DeploymentException("attribute " + xmlName + " not found in schema " + schemaType.getName());
417 }
418 attributeDesc.setXmlType(attribute.getType().getName());
419
420 fields[i] = attributeDesc;
421 } else {
422 ElementDesc elementDesc = new ElementDesc();
423 elementDesc.setFieldName(fieldName);
424 Class javaType = (Class) properties.get(fieldName);
425 if (javaType == null) {
426 //see if it is a public field
427 try {
428 Field field = javaClass.getField(fieldName);
429 javaType = field.getType();
430 } catch (NoSuchFieldException e) {
431 throw new DeploymentException("field name " + fieldName + " not found in " + properties, e);
432 }
433 }
434 QName xmlName = new QName("", variableMapping.getXmlElementName().getStringValue().trim());
435 SchemaParticle particle = (SchemaParticle) paramNameToType.get(xmlName);
436 if (null == particle) {
437 xmlName = new QName(ns, variableMapping.getXmlElementName().getStringValue().trim());
438 particle = (SchemaParticle) paramNameToType.get(xmlName);
439 if (null == particle) {
440 throw new DeploymentException("element " + xmlName + " not found in schema " + schemaType.getName());
441 }
442 } else if (SchemaParticle.ELEMENT != particle.getParticleType()) {
443 throw new DeploymentException(xmlName + " is not an element in schema " + schemaType.getName());
444 }
445 elementDesc.setNillable(particle.isNillable() || hasEncoded);
446 elementDesc.setXmlName(xmlName);
447 if (null != particle.getType().getName()) {
448 elementDesc.setXmlType(particle.getType().getName());
449 } else {
450 QName anonymousName;
451 if (key.isAnonymous()) {
452 anonymousName = new QName(key.getqName().getNamespaceURI(), key.getqName().getLocalPart() +
453 ">" + particle.getName().getLocalPart());
454 } else {
455 anonymousName = new QName(key.getqName().getNamespaceURI(),
456 ">" + key.getqName().getLocalPart() + ">" + particle.getName().getLocalPart());
457 }
458 elementDesc.setXmlType(anonymousName);
459 }
460
461 if (javaType.isArray()) {
462 elementDesc.setMinOccurs(particle.getIntMinOccurs());
463 elementDesc.setMaxOccurs(particle.getIntMaxOccurs());
464 //TODO axis seems to have the wrong name for this property based on how it is used
465 elementDesc.setMaxOccursUnbounded(particle.getIntMaxOccurs() > 1);
466 }
467
468 fields[i] = elementDesc;
469 }
470 }
471 }
472
473 private static class TypeMappingLookup {
474 private static final TypeMappingImpl SOAP_TM = DefaultSOAPEncodingTypeMappingImpl.getSingleton();
475 private static final TypeMappingImpl JAXRPC_TM = DefaultJAXRPC11TypeMappingImpl.getSingleton();
476
477 public static FactoryPair getFactoryPair(QName xmlType) {
478 Class clazz = SOAP_TM.getClassForQName(xmlType, null, null);
479 SerializerFactory sf;
480 DeserializerFactory df;
481 if (null != clazz) {
482 sf = SOAP_TM.getSerializer(clazz, xmlType);
483 df = SOAP_TM.getDeserializer(clazz, xmlType, null);
484 } else {
485 clazz = JAXRPC_TM.getClassForQName(xmlType, null, null);
486 if (null == clazz) {
487 return null;
488 }
489 sf = JAXRPC_TM.getSerializer(clazz, xmlType);
490 df = JAXRPC_TM.getDeserializer(clazz, xmlType, null);
491 }
492 return new FactoryPair(sf.getClass(), df.getClass());
493 }
494 }
495
496 private static class FactoryPair {
497 private final Class serializerFactoryClass;
498 private final Class deserializerFactoryClass;
499
500 private FactoryPair(Class serializerFactoryClass, Class deserializerFactoryClass) {
501 this.serializerFactoryClass = serializerFactoryClass;
502 this.deserializerFactoryClass = deserializerFactoryClass;
503 }
504 }
505 }