/** * 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.basic; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; 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 org.apache.cxf.aegis.DatabindingException; import org.apache.cxf.aegis.type.AegisType; import org.apache.cxf.aegis.type.TypeCreator; import org.apache.cxf.aegis.type.TypeMapping; import org.apache.cxf.common.util.ReflectionUtil; public class BeanTypeInfo { private Map<QName, QName> mappedName2typeName = new HashMap<>(); private Map<QName, String> mappedName2pdName = new HashMap<>(); private Map<QName, AegisType> mappedName2type = new HashMap<>(); private Class<?> beanClass; private List<QName> attributes = new ArrayList<>(); private List<QName> elements = new ArrayList<>(); private PropertyDescriptor[] descriptors; private TypeMapping typeMapping; private volatile boolean initialized; private String defaultNamespace; private int minOccurs; private boolean nillable = true; private boolean isExtension; private boolean qualifyAttributes; private boolean qualifyElements = true; /** * extensibleElements means adding xs:any to WSDL Complex AegisType Definition */ private boolean extensibleElements = true; /** * extensibleAttributes means adding xs:anyAttribute to WSDL Complex AegisType * Definition */ private boolean extensibleAttributes = true; public BeanTypeInfo(Class<?> typeClass, String defaultNamespace) { this.beanClass = typeClass; this.defaultNamespace = defaultNamespace; initializeProperties(); } /** * Create a BeanTypeInfo class. * * @param typeClass * @param defaultNamespace * @param initiallize If true attempt default property/xml mappings. */ public BeanTypeInfo(Class<?> typeClass, String defaultNamespace, boolean initialize) { this.beanClass = typeClass; this.defaultNamespace = defaultNamespace; initializeProperties(); initialized = !initialize; } public String getDefaultNamespace() { return defaultNamespace; } public void initialize() { try { if (!initialized) { initializeSync(); } } catch (Exception e) { if (e instanceof DatabindingException) { throw (DatabindingException)e; } throw new DatabindingException("Couldn't create TypeInfo.", e); } } private synchronized void initializeSync() { if (!initialized) { for (int i = 0; i < descriptors.length; i++) { // Don't map the property unless there is a read property if (isMapped(descriptors[i])) { mapProperty(descriptors[i]); } } initialized = true; } } public boolean isMapped(PropertyDescriptor pd) { return pd.getReadMethod() != null; } protected void mapProperty(PropertyDescriptor pd) { String name = pd.getName(); if (isAttribute(pd)) { mapAttribute(name, createMappedName(pd, qualifyAttributes)); } else if (isElement(pd)) { mapElement(name, createMappedName(pd, qualifyElements)); } } protected PropertyDescriptor[] getPropertyDescriptors() { return descriptors; } protected PropertyDescriptor getPropertyDescriptor(String name) { for (int i = 0; i < descriptors.length; i++) { if (descriptors[i].getName().equals(name)) { return descriptors[i]; } } return null; } /** * Get the type class for the field with the specified QName. */ public AegisType getType(QName name) { // 1. Try a prexisting mapped type AegisType type = mappedName2type.get(name); // 2. Try to get the type by its name, if there is one if (type == null) { QName typeName = getMappedTypeName(name); if (typeName != null) { type = getTypeMapping().getType(typeName); if (type != null) { mapType(name, type); } } } // 3. Create the type from the property descriptor and map it if (type == null) { PropertyDescriptor desc; try { desc = getPropertyDescriptorFromMappedName(name); } catch (Exception e) { if (e instanceof DatabindingException) { throw (DatabindingException)e; } throw new DatabindingException("Couldn't get properties.", e); } if (desc == null) { return null; } try { TypeMapping tm = getTypeMapping(); TypeCreator tc = tm.getTypeCreator(); type = tc.createType(desc); } catch (DatabindingException e) { e.prepend("Couldn't create type for property " + desc.getName() + " on " + getTypeClass()); throw e; } // second part is possible workaround for XFIRE-586 if (registerType(desc)) { getTypeMapping().register(type); } mapType(name, type); } if (type == null) { throw new DatabindingException("Couldn't find type for property " + name); } return type; } protected boolean registerType(PropertyDescriptor desc) { return true; } public void mapType(QName name, AegisType type) { mappedName2type.put(name, type); } private QName getMappedTypeName(QName name) { return mappedName2typeName.get(name); } public TypeMapping getTypeMapping() { return typeMapping; } public void setTypeMapping(TypeMapping typeMapping) { this.typeMapping = typeMapping; } /** * Specifies the name of the property as it shows up in the xml schema. This * method just returns <code>propertyDescriptor.getName();</code> * * @param desc * @return */ protected QName createMappedName(PropertyDescriptor desc, boolean qualified) { if (qualified) { return new QName(getDefaultNamespace(), desc.getName()); } else { return new QName(null, desc.getName()); } } public void mapAttribute(String property, QName mappedName) { mappedName2pdName.put(mappedName, property); attributes.add(mappedName); } public void mapElement(String property, QName mappedName) { mappedName2pdName.put(mappedName, property); elements.add(mappedName); } /** * Specifies the SchemaType for a particular class. * * @param mappedName * @param type */ public void mapTypeName(QName mappedName, QName type) { mappedName2typeName.put(mappedName, type); } private void initializeProperties() { BeanInfo beanInfo = null; try { if (beanClass.isInterface() || beanClass.isPrimitive()) { descriptors = getInterfacePropertyDescriptors(beanClass); } else if (beanClass.isEnum()) { // do nothing } else if (beanClass == Object.class || beanClass == Throwable.class) { // do nothing } else if (RuntimeException.class.isAssignableFrom(beanClass)) { beanInfo = Introspector.getBeanInfo(beanClass, RuntimeException.class); } else if (Exception.class.isAssignableFrom(beanClass) && !Exception.class.equals(beanClass)) { beanInfo = Introspector.getBeanInfo(beanClass, Exception.class); } else if (Throwable.class.isAssignableFrom(beanClass)) { beanInfo = Introspector.getBeanInfo(beanClass, Throwable.class); } else { beanInfo = Introspector.getBeanInfo(beanClass, Object.class); } } catch (IntrospectionException e) { throw new DatabindingException("Couldn't introspect interface.", e); } if (beanInfo != null) { PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); if (propertyDescriptors != null) { // see comments on this function. descriptors = ReflectionUtil.getPropertyDescriptorsAvoidSunBug(getClass(), beanInfo, beanClass, propertyDescriptors); } } if (descriptors == null) { descriptors = new PropertyDescriptor[0]; } Arrays.sort(descriptors, new Comparator<PropertyDescriptor>() { public int compare(PropertyDescriptor o1, PropertyDescriptor o2) { return o1.getName().compareTo(o2.getName()); } }); } private PropertyDescriptor[] getInterfacePropertyDescriptors(Class<?> clazz) { List<PropertyDescriptor> pds = new ArrayList<>(); getInterfacePropertyDescriptors(clazz, pds, new HashSet<Class<?>>()); return pds.toArray(new PropertyDescriptor[pds.size()]); } private void getInterfacePropertyDescriptors(Class<?> clazz, List<PropertyDescriptor> pds, Set<Class<?>> classes) { if (classes.contains(clazz)) { return; } classes.add(clazz); try { Class<?>[] interfaces = clazz.getInterfaces(); /** * add base interface information */ BeanInfo info = Introspector.getBeanInfo(clazz); for (int j = 0; j < info.getPropertyDescriptors().length; j++) { PropertyDescriptor pd = info.getPropertyDescriptors()[j]; if (!containsPropertyName(pds, pd.getName())) { pds.add(pd); } } /** * add extended interface information */ for (int i = 0; i < interfaces.length; i++) { getInterfacePropertyDescriptors(interfaces[i], pds, classes); } } catch (IntrospectionException e) { // do nothing } } private boolean containsPropertyName(List<PropertyDescriptor> pds, String name) { for (Iterator<PropertyDescriptor> itr = pds.iterator(); itr.hasNext();) { PropertyDescriptor pd = itr.next(); if (pd.getName().equals(name)) { return true; } } return false; } public PropertyDescriptor getPropertyDescriptorFromMappedName(QName name) { return getPropertyDescriptor(getPropertyNameFromMappedName(name)); } protected boolean isAttribute(PropertyDescriptor desc) { return false; } protected boolean isElement(PropertyDescriptor desc) { return true; } protected boolean isSerializable(PropertyDescriptor desc) { return true; } protected Class<?> getTypeClass() { return beanClass; } /** * Nillable is only allowed if the actual property is Nullable * * @param name * @return */ public boolean isNillable(QName name) { AegisType type = getType(name); if (!type.isNillable()) { return false; } return nillable; } /** * Return the minOccurs value. When there is no XML file or annotation (the situation * if we are running from the base class here), there is no source for the * minOccurs parameter except the default, which is supplied from the overall Aegis options. * @param name Element QName * @return */ public int getMinOccurs(QName name) { return minOccurs; } /** * Return the maxOccurs value. When there is no XML file or annotation (the situation * if we are in the base class here), there is no per-element source for this item, * and the value is always 1. * @param name Element QName * @return 1 */ public int getMaxOccurs(QName name) { return 1; } public long getMinOccurs() { return minOccurs; } public void setDefaultMinOccurs(int m) { this.minOccurs = m; } public void setDefaultNillable(boolean n) { this.nillable = n; } private String getPropertyNameFromMappedName(QName name) { return mappedName2pdName.get(name); } public List<QName> getAttributes() { return attributes; } public List<QName> getElements() { return elements; } public boolean isExtensibleElements() { return extensibleElements; } public void setExtensibleElements(boolean futureProof) { this.extensibleElements = futureProof; } public boolean isExtensibleAttributes() { return extensibleAttributes; } public void setExtensibleAttributes(boolean extensibleAttributes) { this.extensibleAttributes = extensibleAttributes; } public void setExtension(boolean extension) { this.isExtension = extension; } public boolean isExtension() { return isExtension; } /** * @return Returns the qualifyAttributes. */ public boolean isQualifyAttributes() { return qualifyAttributes; } /** * @param qualifyAttributes The qualifyAttributes to set. */ public void setQualifyAttributes(boolean qualifyAttributes) { this.qualifyAttributes = qualifyAttributes; } /** * @return Returns the qualifyElements. */ public boolean isQualifyElements() { return qualifyElements; } /** * @param qualifyElements The qualifyElements to set. */ public void setQualifyElements(boolean qualifyElements) { this.qualifyElements = qualifyElements; } }