/** * 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.cxf.aegis.type; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Collection; import java.util.Map; import javax.xml.namespace.QName; import org.apache.cxf.aegis.DatabindingException; import org.apache.cxf.aegis.type.basic.ArrayType; import org.apache.cxf.aegis.type.basic.ObjectType; import org.apache.cxf.aegis.type.collection.CollectionType; import org.apache.cxf.aegis.type.collection.MapType; import org.apache.cxf.aegis.util.NamespaceHelper; import org.apache.cxf.aegis.util.ServiceUtils; import org.apache.cxf.wsdl.WSDLConstants; import org.apache.ws.commons.schema.constants.Constants; public abstract class AbstractTypeCreator implements TypeCreator { public static final String HTTP_CXF_APACHE_ORG_ARRAYS = "http://cxf.apache.org/arrays"; protected TypeMapping tm; protected AbstractTypeCreator nextCreator; private TypeCreationOptions typeConfiguration; private TypeCreator parent; public TypeMapping getTypeMapping() { return tm; } public TypeCreator getTopCreator() { TypeCreator top = this; TypeCreator next = top; while (next != null) { top = next; next = top.getParent(); } return top; } public TypeCreator getParent() { return parent; } public void setParent(TypeCreator parent) { this.parent = parent; } public void setTypeMapping(TypeMapping typeMapping) { this.tm = typeMapping; if (nextCreator != null) { nextCreator.setTypeMapping(tm); } } public void setNextCreator(AbstractTypeCreator creator) { this.nextCreator = creator; nextCreator.parent = this; } public TypeClassInfo createClassInfo(Field f) { TypeClassInfo info = createBasicClassInfo(f.getType()); info.setDescription("field " + f.getName() + " in " + f.getDeclaringClass()); return info; } public TypeClassInfo createBasicClassInfo(Type type) { TypeClassInfo info = new TypeClassInfo(); Class<?> typeClass = TypeUtil.getTypeClass(type, false); if (typeClass != null) { info.setDescription("class '" + typeClass.getName() + "'"); } else { info.setDescription("type '" + type + "'"); } info.setType(type); return info; } public AegisType createTypeForClass(TypeClassInfo info) { Class<?> javaClass = TypeUtil.getTypeRelatedClass(info.getType()); AegisType result = null; boolean newType = true; if (info.getType() instanceof TypeVariable) { //it's the generic type result = getOrCreateGenericType(info); } else if (info.getAegisTypeClass() != null) { result = createUserType(info); } else if (isArray(javaClass)) { result = createArrayType(info); } else if (isMap(javaClass)) { result = createMapType(info); } else if (isHolder(javaClass)) { result = createHolderType(info); } else if (isCollection(javaClass)) { result = createCollectionType(info); } else if (isEnum(javaClass)) { result = createEnumType(info); } else if (javaClass.equals(byte[].class)) { result = getTypeMapping().getType(javaClass); } else { AegisType type = getTypeMapping().getType(info.getType()); if (type == null || (info.getTypeName() != null && !type.getSchemaType().equals(info.getTypeName()))) { if (info.getTypeName() != null) { type = getTypeMapping().getType(info.getTypeName()); } if (type == null) { type = getTypeMapping().getType(javaClass); } if (type == null) { type = createDefaultType(info); } else { newType = false; } } else { newType = false; } result = type; } if (newType && !getConfiguration().isDefaultNillable()) { result.setNillable(false); } return result; } protected boolean isHolder(Class<?> javaType) { return "javax.xml.ws.Holder".equals(javaType.getName()); } protected AegisType createHolderType(TypeClassInfo info) { Type heldType = TypeUtil.getSingleTypeParameter(info.getType(), 0); if (heldType == null) { throw new UnsupportedOperationException("Invalid holder type " + info.getType()); } info.setType(heldType); return createType(heldType); } protected boolean isArray(Class<?> javaType) { return javaType.isArray() && !javaType.equals(byte[].class); } protected AegisType createUserType(TypeClassInfo info) { try { AegisType type = info.getAegisTypeClass().newInstance(); QName name = info.getTypeName(); if (name == null) { // We do not want to use the java.lang.whatever schema type. // If the @ annotation or XML file didn't specify a schema type, // but the natural type has a schema type mapping, we use that rather // than create nonsense. Class<?> typeClass = TypeUtil.getTypeRelatedClass(info.getType()); if (typeClass.getPackage().getName().startsWith("java")) { name = tm.getTypeQName(typeClass); } // if it's still null, we'll take our lumps, but probably end up with // an invalid schema. if (name == null) { name = createQName(typeClass); } } type.setSchemaType(name); type.setTypeClass(info.getType()); type.setTypeMapping(getTypeMapping()); return type; } catch (InstantiationException e) { throw new DatabindingException("Couldn't instantiate type classs " + info.getAegisTypeClass().getName(), e); } catch (IllegalAccessException e) { throw new DatabindingException("Couldn't access type classs " + info.getAegisTypeClass().getName(), e); } } protected AegisType createArrayType(TypeClassInfo info) { ArrayType type = new ArrayType(); type.setTypeMapping(getTypeMapping()); type.setTypeClass(info.getType()); type.setSchemaType(createCollectionQName(info, type.getComponentType())); if (info.getMinOccurs() != -1) { type.setMinOccurs(info.getMinOccurs()); } else { type.setMinOccurs(typeConfiguration.getDefaultMinOccurs()); } if (info.getMaxOccurs() != -1) { type.setMaxOccurs(info.getMaxOccurs()); } type.setFlat(info.isFlat()); return type; } protected QName createQName(Class<?> javaType) { String clsName = javaType.getName(); String ns = NamespaceHelper.makeNamespaceFromClassName(clsName, "http"); String localName = ServiceUtils.makeServiceNameFromClassName(javaType); return new QName(ns, localName); } protected boolean isCollection(Class<?> javaType) { return Collection.class.isAssignableFrom(javaType); } protected AegisType createCollectionTypeFromGeneric(TypeClassInfo info) { AegisType component = getOrCreateGenericType(info); CollectionType type = new CollectionType(component); type.setTypeMapping(getTypeMapping()); QName name = info.getTypeName(); if (name == null) { name = createCollectionQName(info, component); } type.setSchemaType(name); type.setTypeClass(info.getType()); if (info.getMinOccurs() != -1) { type.setMinOccurs(info.getMinOccurs()); } if (info.getMaxOccurs() != -1) { type.setMaxOccurs(info.getMaxOccurs()); } type.setFlat(info.isFlat()); return type; } protected AegisType getOrCreateGenericType(TypeClassInfo info) { return createObjectType(); } protected AegisType getOrCreateMapKeyType(TypeClassInfo info) { return nextCreator.getOrCreateMapKeyType(info); } protected AegisType createObjectType() { ObjectType type = new ObjectType(); type.setSchemaType(Constants.XSD_ANYTYPE); type.setTypeClass(Object.class); type.setTypeMapping(getTypeMapping()); return type; } protected AegisType getOrCreateMapValueType(TypeClassInfo info) { return nextCreator.getOrCreateMapValueType(info); } protected AegisType createMapType(TypeClassInfo info, AegisType keyType, AegisType valueType) { QName schemaType = createMapQName(info, keyType, valueType); MapType type = new MapType(schemaType, keyType, valueType); type.setTypeMapping(getTypeMapping()); type.setTypeClass(info.getType()); return type; } protected AegisType createMapType(TypeClassInfo info) { AegisType keyType = getOrCreateMapKeyType(info); AegisType valueType = getOrCreateMapValueType(info); return createMapType(info, keyType, valueType); } protected QName createMapQName(TypeClassInfo info, AegisType keyType, AegisType valueType) { String name = keyType.getSchemaType().getLocalPart() + '2' + valueType.getSchemaType().getLocalPart(); Class<?> cls = TypeUtil.getTypeRelatedClass(info.getType()); name += cls.getSimpleName(); // TODO: Get namespace from XML? return new QName(tm.getMappingIdentifierURI(), name); } protected boolean isMap(Class<?> javaType) { return Map.class.isAssignableFrom(javaType); } public abstract TypeClassInfo createClassInfo(PropertyDescriptor pd); protected boolean isEnum(Class<?> javaType) { return false; } public AegisType createEnumType(TypeClassInfo info) { return null; } public abstract AegisType createCollectionType(TypeClassInfo info); public abstract AegisType createDefaultType(TypeClassInfo info); protected QName createCollectionQName(TypeClassInfo info, AegisType type) { String ns; if (type.isComplex()) { ns = type.getSchemaType().getNamespaceURI(); } else { ns = tm.getMappingIdentifierURI(); } if (WSDLConstants.NS_SCHEMA_XSD.equals(ns)) { ns = HTTP_CXF_APACHE_ORG_ARRAYS; } String first = type.getSchemaType().getLocalPart().substring(0, 1); String last = type.getSchemaType().getLocalPart().substring(1); String localName = "ArrayOf" + first.toUpperCase() + last; if (info.nonDefaultAttributes()) { localName += "-"; if (info.getMinOccurs() >= 0) { localName += info.getMinOccurs(); } localName += "-"; if (info.getMaxOccurs() >= 0) { localName += info.getMaxOccurs(); } if (info.isFlat()) { localName += "Flat"; } } return new QName(ns, localName); } public abstract TypeClassInfo createClassInfo(Method m, int index); /** * Create a AegisType for a Method parameter. * * @param m the method to create a type for * @param index The parameter index. If the index is less than zero, the * return type is used. */ public AegisType createType(Method m, int index) { TypeClassInfo info = createClassInfo(m, index); info.setDescription((index == -1 ? "return type" : "parameter " + index) + " of method " + m.getName() + " in " + m.getDeclaringClass()); return createTypeForClass(info); } public QName getElementName(Method m, int index) { TypeClassInfo info = createClassInfo(m, index); return info.getMappedName(); } /** * Create type information for a PropertyDescriptor. * * @param pd the propertydescriptor */ public AegisType createType(PropertyDescriptor pd) { TypeClassInfo info = createClassInfo(pd); info.setDescription("property " + pd.getName()); return createTypeForClass(info); } /** * Create type information for a <code>Field</code>. * * @param f the field to create a type from */ public AegisType createType(Field f) { TypeClassInfo info = createClassInfo(f); info.setDescription("field " + f.getName() + " in " + f.getDeclaringClass()); return createTypeForClass(info); } /** * Create an Aegis type from a reflected type description. * This will only work for the restricted set of collection * types supported by Aegis. * @param t the reflected type. * @return the type */ public AegisType createType(Type t) { TypeClassInfo info = new TypeClassInfo(); info.setType(t); info.setDescription("reflected type " + t.toString()); return createTypeForClass(info); } public AegisType createType(Class<?> clazz) { TypeClassInfo info = createBasicClassInfo(clazz); info.setDescription(clazz.toString()); return createTypeForClass(info); } public TypeCreationOptions getConfiguration() { return typeConfiguration; } public void setConfiguration(TypeCreationOptions tpConfiguration) { this.typeConfiguration = tpConfiguration; } }