| /** |
| * 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.io.IOException; |
| import java.io.InputStream; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.ParameterizedType; |
| import java.lang.reflect.Type; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import javax.xml.XMLConstants; |
| import javax.xml.namespace.QName; |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.transform.stream.StreamSource; |
| import javax.xml.validation.Schema; |
| import javax.xml.validation.SchemaFactory; |
| import javax.xml.xpath.XPathConstants; |
| |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| |
| import org.xml.sax.ErrorHandler; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| |
| import org.apache.cxf.aegis.DatabindingException; |
| import org.apache.cxf.aegis.type.basic.BeanType; |
| import org.apache.cxf.aegis.type.basic.XMLBeanTypeInfo; |
| import org.apache.cxf.aegis.type.java5.Java5TypeCreator; |
| import org.apache.cxf.aegis.util.NamespaceHelper; |
| import org.apache.cxf.common.classloader.ClassLoaderUtils; |
| import org.apache.cxf.common.logging.LogUtils; |
| import org.apache.cxf.helpers.DOMUtils; |
| import org.apache.cxf.helpers.XPathUtils; |
| |
| /** |
| * Deduce mapping information from an xml file. The xml file should be in the |
| * same packages as the class, with the name <code>className.aegis.xml</code>. |
| * For example, given the following service interface: <p/> |
| * |
| * <pre> |
| * public Collection getResultsForValues(String id, Collection values); //method 1 |
| * |
| * public Collection getResultsForValues(int id, Collection values); //method 2 |
| * |
| * public String getResultForValue(String value); //method 3 |
| * </pre> |
| * |
| * An example of the type xml is: |
| * |
| * <pre> |
| * <mappings> |
| * <mapping> |
| * <method name="getResultsForValues"> |
| * <return-type componentType="com.acme.ResultBean" /> |
| * <!-- no need to specify index 0, since it's a String --> |
| * <parameter index="1" componentType="java.lang.String" /> |
| * </method> |
| * </mapping> |
| * </mappings> |
| * </pre> |
| * |
| * <p/> Note that for values which can be easily deduced (such as the String |
| * parameter, or the second service method) no mapping need be specified in the |
| * xml descriptor, which is why no mapping is specified for method 3. <p/> |
| * However, if you have overloaded methods with different semantics, then you |
| * will need to specify enough parameters to disambiguate the method and |
| * uniquely identify it. So in the example above, the mapping specifies will |
| * apply to both method 1 and method 2, since the parameter at index 0 is not |
| * specified. |
| */ |
| public class XMLTypeCreator extends AbstractTypeCreator { |
| private static final Logger LOG = LogUtils.getL7dLogger(XMLTypeCreator.class); |
| private static List<Class<?>> stopClasses = new ArrayList<Class<?>>(); |
| static { |
| stopClasses.add(Object.class); |
| stopClasses.add(Exception.class); |
| stopClasses.add(RuntimeException.class); |
| stopClasses.add(Throwable.class); |
| } |
| |
| private static final DocumentBuilderFactory AEGIS_DOCUMENT_BUILDER_FACTORY; |
| // cache of classes to documents |
| private Map<String, Document> documents = new HashMap<String, Document>(); |
| static { |
| AEGIS_DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); |
| AEGIS_DOCUMENT_BUILDER_FACTORY.setNamespaceAware(true); |
| |
| String path = "/META-INF/cxf/aegis.xsd"; |
| InputStream is = XMLTypeCreator.class.getResourceAsStream(path); |
| if (is != null) { |
| try { |
| SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); |
| Schema aegisSchema = schemaFactory.newSchema(new StreamSource(is)); |
| AEGIS_DOCUMENT_BUILDER_FACTORY.setSchema(aegisSchema); |
| } catch (Throwable e) { |
| LOG.log(Level.INFO, "Could not set aegis schema. Not validating.", e); |
| } finally { |
| try { |
| is.close(); |
| } catch (IOException ex) { |
| //ignore |
| } |
| } |
| } |
| } |
| |
| private volatile XPathUtils xpathUtils; |
| private synchronized XPathUtils getXPathUtils() { |
| if (xpathUtils == null) { |
| xpathUtils = new XPathUtils(); |
| } |
| return xpathUtils; |
| } |
| |
| private Document readAegisFile(InputStream is, final String path) throws IOException { |
| DocumentBuilder documentBuilder; |
| try { |
| documentBuilder = AEGIS_DOCUMENT_BUILDER_FACTORY.newDocumentBuilder(); |
| } catch (ParserConfigurationException e) { |
| LOG.log(Level.SEVERE, "Unable to create a document builder, e"); |
| throw new RuntimeException("Unable to create a document builder, e"); |
| } |
| org.w3c.dom.Document doc; |
| documentBuilder.setErrorHandler(new ErrorHandler() { |
| |
| private String errorMessage(SAXParseException exception) { |
| return MessageFormat.format("{0} at {1} line {2} column {3}.", |
| new Object[] {exception.getMessage(), path, |
| Integer.valueOf(exception.getLineNumber()), |
| Integer.valueOf(exception.getColumnNumber())}); |
| } |
| |
| private void throwDatabindingException(String message) { |
| //DatabindingException is quirky. This dance is required to get the full message |
| //to where it belongs. |
| DatabindingException e = new DatabindingException(message); |
| e.setMessage(message); |
| throw e; |
| } |
| |
| public void error(SAXParseException exception) throws SAXException { |
| String message = errorMessage(exception); |
| LOG.log(Level.SEVERE, message, exception); |
| throwDatabindingException(message); |
| |
| } |
| |
| public void fatalError(SAXParseException exception) throws SAXException { |
| String message = errorMessage(exception); |
| LOG.log(Level.SEVERE, message, exception); |
| throwDatabindingException(message); |
| } |
| |
| public void warning(SAXParseException exception) throws SAXException { |
| LOG.log(Level.INFO, errorMessage(exception), exception); |
| } |
| }); |
| |
| try { |
| doc = documentBuilder.parse(is); |
| } catch (SAXException e) { |
| LOG.log(Level.SEVERE, "Error parsing Aegis file.", e); // can't happen due to |
| // above. |
| return null; |
| } |
| return doc; |
| } |
| |
| protected Document getDocument(Class<?> clazz) { |
| if (clazz == null) { |
| return null; |
| } |
| if (documents.containsKey(clazz.getName())) { |
| return documents.get(clazz.getName()); |
| } |
| String path = '/' + clazz.getName().replace('.', '/') + ".aegis.xml"; |
| InputStream is = clazz.getResourceAsStream(path); |
| if (is == null) { |
| documents.put(clazz.getName(), null); |
| LOG.finest("Mapping file : " + path + " not found."); |
| return null; |
| } |
| LOG.finest("Found mapping file : " + path); |
| try { |
| Document doc = readAegisFile(is, path); |
| documents.put(clazz.getName(), doc); |
| return doc; |
| } catch (IOException e) { |
| LOG.log(Level.SEVERE, "Error loading file " + path, e); |
| documents.put(clazz.getName(), null); |
| return null; |
| } |
| } |
| |
| @Override |
| protected boolean isEnum(Class<?> javaType) { |
| Element mapping = findMapping(javaType); |
| if (mapping != null) { |
| return super.isEnum(javaType); |
| } else { |
| return nextCreator.isEnum(javaType); |
| } |
| } |
| |
| @Override |
| public AegisType createEnumType(TypeClassInfo info) { |
| Element mapping = findMapping(info.getType()); |
| if (mapping != null) { |
| return super.createEnumType(info); |
| } else { |
| return nextCreator.createEnumType(info); |
| } |
| } |
| |
| @Override |
| public AegisType createCollectionType(TypeClassInfo info) { |
| /* If it is a parameterized type, then we already know |
| * the parameter(s) and we don't need to fish them out of the XML. |
| */ |
| if (info.getType() instanceof Class) { |
| return createCollectionTypeFromGeneric(info); |
| } |
| |
| return nextCreator.createCollectionType(info); |
| } |
| |
| @Override |
| public TypeClassInfo createClassInfo(PropertyDescriptor pd) { |
| Element mapping = findMapping(pd.getReadMethod().getDeclaringClass()); |
| if (mapping == null) { |
| return nextCreator.createClassInfo(pd); |
| } |
| |
| Element propertyEl = getMatch(mapping, "./property[@name='" + pd.getName() + "']"); |
| if (propertyEl == null) { |
| return nextCreator.createClassInfo(pd); |
| } |
| |
| TypeClassInfo info = new TypeClassInfo(); |
| Type returnType = pd.getReadMethod().getGenericReturnType(); |
| info.setType(returnType); |
| info.setDescription("property " + pd.getDisplayName()); |
| readMetadata(info, mapping, propertyEl); |
| |
| return info; |
| } |
| |
| protected Element findMapping(Type type) { |
| // We are not prepared to find .aegis.xml files for Parameterized types. |
| |
| Class<?> clazz = TypeUtil.getTypeClass(type, false); |
| |
| if (clazz == null) { |
| return null; |
| } |
| Document doc = getDocument(clazz); |
| if (doc == null) { |
| return null; |
| } |
| |
| Element mapping = getMatch(doc, "/mappings/mapping[@uri='" |
| + getTypeMapping().getMappingIdentifierURI() |
| + "']"); |
| if (mapping == null) { |
| mapping = getMatch(doc, "/mappings/mapping[not(@uri)]"); |
| } |
| |
| return mapping; |
| } |
| |
| protected List<Element> findMappings(Type type) { |
| Class<?> clazz = TypeUtil.getTypeClass(type, false); |
| List<Element> mappings = new ArrayList<Element>(); |
| if (clazz == null) { |
| return mappings; |
| } |
| |
| Element top = findMapping(clazz); |
| if (top != null) { |
| mappings.add(top); |
| } |
| |
| Class<?> parent = clazz; |
| while (true) { |
| |
| // Read mappings for interfaces as well |
| Class<?>[] interfaces = parent.getInterfaces(); |
| for (int i = 0; i < interfaces.length; i++) { |
| Class<?> interfaze = interfaces[i]; |
| List<Element> interfaceMappings = findMappings(interfaze); |
| mappings.addAll(interfaceMappings); |
| } |
| |
| Class<?> sup = parent.getSuperclass(); |
| |
| if (sup == null || stopClasses.contains(sup)) { |
| break; |
| } |
| |
| Element mapping = findMapping(sup); |
| if (mapping != null) { |
| mappings.add(mapping); |
| } |
| |
| parent = sup; |
| } |
| |
| return mappings; |
| } |
| |
| @Override |
| public AegisType createDefaultType(TypeClassInfo info) { |
| Element mapping = findMapping(info.getType()); |
| List<Element> mappings = findMappings(info.getType()); |
| Class<?> relatedClass = TypeUtil.getTypeRelatedClass(info.getType()); |
| |
| if (mapping != null || mappings.size() > 0) { |
| String typeNameAtt = null; |
| if (mapping != null) { |
| typeNameAtt = DOMUtils.getAttributeValueEmptyNull(mapping, "name"); |
| } |
| |
| String extensibleElements = null; |
| if (mapping != null) { |
| extensibleElements = mapping.getAttribute("extensibleElements"); |
| } |
| |
| String extensibleAttributes = null; |
| if (mapping != null) { |
| extensibleAttributes = mapping.getAttribute("extensibleAttributes"); |
| } |
| |
| String defaultNS = NamespaceHelper.makeNamespaceFromClassName(relatedClass.getName(), "http"); |
| QName name = null; |
| if (typeNameAtt != null) { |
| name = NamespaceHelper.createQName(mapping, typeNameAtt, defaultNS); |
| |
| defaultNS = name.getNamespaceURI(); |
| } |
| |
| // We do not deal with Generic beans at this point. |
| XMLBeanTypeInfo btinfo = new XMLBeanTypeInfo(relatedClass, mappings, defaultNS); |
| btinfo.setTypeMapping(getTypeMapping()); |
| btinfo.setDefaultMinOccurs(getConfiguration().getDefaultMinOccurs()); |
| btinfo.setDefaultNillable(getConfiguration().isDefaultNillable()); |
| |
| if (extensibleElements != null) { |
| btinfo.setExtensibleElements(Boolean.valueOf(extensibleElements).booleanValue()); |
| } else { |
| btinfo.setExtensibleElements(getConfiguration().isDefaultExtensibleElements()); |
| } |
| |
| if (extensibleAttributes != null) { |
| btinfo.setExtensibleAttributes(Boolean.valueOf(extensibleAttributes).booleanValue()); |
| } else { |
| btinfo.setExtensibleAttributes(getConfiguration().isDefaultExtensibleAttributes()); |
| } |
| |
| btinfo.setQualifyAttributes(this.getConfiguration().isQualifyAttributes()); |
| btinfo.setQualifyElements(this.getConfiguration().isQualifyElements()); |
| BeanType type = new BeanType(btinfo); |
| |
| if (name == null) { |
| name = createQName(relatedClass); |
| } |
| |
| type.setSchemaType(name); |
| |
| type.setTypeClass(info.getType()); |
| type.setTypeMapping(getTypeMapping()); |
| |
| return type; |
| } else { |
| return nextCreator.createDefaultType(info); |
| } |
| } |
| |
| @Override |
| public TypeClassInfo createClassInfo(Method m, int index) { |
| Element mapping = findMapping(m.getDeclaringClass()); |
| |
| if (mapping == null) { |
| return nextCreator.createClassInfo(m, index); |
| } |
| |
| // find the elements that apply to the specified method |
| TypeClassInfo info = nextCreator.createClassInfo(m, index); // start |
| // with the |
| // java5 |
| // (or whatever) version. |
| if (info == null) { |
| info = new TypeClassInfo(); |
| } |
| |
| info.setDescription("method " + m.getName() + " parameter " + index); |
| if (index >= 0) { |
| if (index >= m.getParameterTypes().length) { |
| throw new DatabindingException("Method " |
| + m |
| + " does not have a parameter at index " |
| + index); |
| } |
| // we don't want nodes for which the specified index is not |
| // specified |
| List<Element> nodes = getMatches(mapping, "./method[@name='" + m.getName() |
| + "']/parameter[@index='" + index + "']/parent::*"); |
| if (nodes.size() == 0) { |
| // no mapping for this method |
| return info; |
| } |
| // pick the best matching node |
| Element bestMatch = getBestMatch(mapping, m, nodes); |
| |
| if (bestMatch == null) { |
| // no mapping for this method |
| return info; |
| } |
| info.setType(m.getGenericParameterTypes()[index]); |
| // info.setAnnotations(m.getParameterAnnotations()[index]); |
| Element parameter = getMatch(bestMatch, "parameter[@index='" + index + "']"); |
| readMetadata(info, mapping, parameter); |
| } else { |
| List<Element> nodes = getMatches(mapping, "./method[@name='" + m.getName() |
| + "']/return-type/parent::*"); |
| if (nodes.size() == 0) { |
| return info; |
| } |
| Element bestMatch = getBestMatch(mapping, m, nodes); |
| if (bestMatch == null) { |
| // no mapping for this method |
| return info; |
| } |
| info.setType(m.getGenericReturnType()); |
| // info.setAnnotations(m.getAnnotations()); |
| Element rtElement = DOMUtils.getFirstChildWithName(bestMatch, "", "return-type"); |
| readMetadata(info, mapping, rtElement); |
| } |
| |
| return info; |
| } |
| |
| protected void readMetadata(TypeClassInfo info, Element mapping, Element parameter) { |
| info.setTypeName(createQName(parameter, DOMUtils.getAttributeValueEmptyNull(parameter, "typeName"))); |
| info.setMappedName(createQName(parameter, |
| DOMUtils.getAttributeValueEmptyNull(parameter, "mappedName"))); |
| Class<?> relatedClass = TypeUtil.getTypeRelatedClass(info.getType()); |
| // we only mess with the generic issues for list and map |
| if (Collection.class.isAssignableFrom(relatedClass)) { |
| Type componentType = getComponentType(mapping, parameter); |
| if (componentType != null) { // there is actually XML config. |
| Type fullType = ParameterizedTypeFactory.createParameterizedType(relatedClass, |
| new Type[] {componentType}); |
| info.setType(fullType); |
| } |
| } else if (Map.class.isAssignableFrom(relatedClass)) { |
| Type keyType = getKeyType(mapping, parameter); |
| if (keyType != null) { |
| info.setKeyType(keyType); |
| } |
| Type valueType = getValueType(mapping, parameter); |
| if (valueType != null) { |
| info.setValueType(valueType); |
| } |
| // if the XML only specifies one, we expect the other to come from a full |
| // parameterized type. |
| if (keyType != null || valueType != null) { |
| if (keyType == null || valueType == null) { |
| if (keyType == null) { |
| keyType = TypeUtil.getSingleTypeParameter(info.getType(), 0); |
| } |
| if (keyType == null) { |
| keyType = Object.class; |
| } |
| if (valueType == null) { |
| valueType = TypeUtil.getSingleTypeParameter(info.getType(), 1); |
| } |
| if (valueType == null) { |
| valueType = Object.class; |
| } |
| } |
| Type fullType |
| = ParameterizedTypeFactory.createParameterizedType(relatedClass, |
| new Type[] {keyType, valueType}); |
| info.setType(fullType); |
| |
| } |
| |
| |
| } |
| setType(info, parameter); |
| |
| String min = DOMUtils.getAttributeValueEmptyNull(parameter, "minOccurs"); |
| if (min != null) { |
| info.setMinOccurs(Long.parseLong(min)); |
| } |
| |
| String max = DOMUtils.getAttributeValueEmptyNull(parameter, "maxOccurs"); |
| if (max != null) { |
| info.setMaxOccurs(Long.parseLong(max)); |
| } |
| |
| String flat = DOMUtils.getAttributeValueEmptyNull(parameter, "flat"); |
| if (flat != null) { |
| info.setFlat(Boolean.valueOf(flat.toLowerCase()).booleanValue()); |
| } |
| |
| String nillable = DOMUtils.getAttributeValueEmptyNull(parameter, "nillable"); |
| if (nillable != null) { |
| info.setNillable(Boolean.valueOf(nillable.toLowerCase()).booleanValue()); |
| } |
| } |
| |
| @Override |
| protected AegisType getOrCreateGenericType(TypeClassInfo info) { |
| AegisType type = null; |
| if (info.getType() instanceof ParameterizedType) { |
| type = createTypeFromGeneric(info.getType()); |
| } |
| |
| if (type == null) { |
| type = super.getOrCreateGenericType(info); |
| } |
| |
| return type; |
| } |
| |
| private AegisType createTypeFromGeneric(Object cType) { |
| if (cType instanceof TypeClassInfo) { |
| return createTypeForClass((TypeClassInfo)cType); |
| } else if (cType instanceof Class) { |
| return createType((Class<?>)cType); |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| protected AegisType getOrCreateMapKeyType(TypeClassInfo info) { |
| AegisType type = null; |
| if (info.getKeyType() != null) { |
| type = createTypeFromGeneric(info.getKeyType()); |
| } |
| |
| if (type == null) { |
| type = super.getOrCreateMapKeyType(info); |
| } |
| |
| return type; |
| } |
| |
| @Override |
| protected AegisType getOrCreateMapValueType(TypeClassInfo info) { |
| AegisType type = null; |
| if (info.getType() instanceof ParameterizedType) { |
| // well, let's hope that someone has filled in the value type. |
| type = createTypeFromGeneric(info.getValueType()); |
| } |
| |
| if (type == null) { |
| type = super.getOrCreateMapValueType(info); |
| } |
| |
| return type; |
| } |
| |
| private Type getComponentType(Element mapping, Element parameter) { |
| String componentSpec = DOMUtils.getAttributeValueEmptyNull(parameter, "componentType"); |
| if (componentSpec == null) { |
| return null; |
| } |
| return getGenericParameterFromSpec(mapping, componentSpec); |
| } |
| |
| private Type getKeyType(Element mapping, Element parameter) { |
| String spec = DOMUtils.getAttributeValueEmptyNull(parameter, "keyType"); |
| if (spec == null) { |
| return null; |
| } |
| return getGenericParameterFromSpec(mapping, spec); |
| } |
| |
| private Type getValueType(Element mapping, Element parameter) { |
| String spec = DOMUtils.getAttributeValueEmptyNull(parameter, "valueType"); |
| if (spec == null) { |
| return null; |
| } |
| return getGenericParameterFromSpec(mapping, spec); |
| } |
| |
| // This cannot do List<List<x>>. |
| private Type getGenericParameterFromSpec(Element mapping, String componentType) { |
| if (componentType.startsWith("#")) { |
| String name = componentType.substring(1); |
| Element propertyEl = getMatch(mapping, "./component[@name='" + name + "']"); |
| if (propertyEl == null) { |
| throw new DatabindingException("Could not find <component> element in mapping named '" + name |
| + "'"); |
| } |
| |
| String className = DOMUtils.getAttributeValueEmptyNull(propertyEl, "class"); |
| if (className == null) { |
| throw new DatabindingException("A 'class' attribute must be specified for <component> " |
| + name); |
| } |
| |
| return loadComponentClass(className); |
| } else { |
| return loadComponentClass(componentType); |
| } |
| } |
| |
| private Class<?> loadComponentClass(String componentType) { |
| try { |
| return ClassLoaderUtils.loadClass(componentType, getClass()); |
| } catch (ClassNotFoundException e) { |
| throw new DatabindingException("Unable to load component type class " + componentType, e); |
| } |
| } |
| |
| protected void setType(TypeClassInfo info, Element parameter) { |
| String type = DOMUtils.getAttributeValueEmptyNull(parameter, "type"); |
| if (type != null) { |
| try { |
| Class<?> aegisTypeClass = ClassLoaderUtils.loadClass(type, getClass()); |
| info.setAegisTypeClass(Java5TypeCreator.castToAegisTypeClass(aegisTypeClass)); |
| } catch (ClassNotFoundException e) { |
| throw new DatabindingException("Unable to load type class " + type, e); |
| } |
| } |
| } |
| |
| |
| |
| private Element getBestMatch(Element mapping, Method method, List<Element> availableNodes) { |
| // first find all the matching method names |
| List<Element> nodes = getMatches(mapping, "./method[@name='" + method.getName() + "']"); |
| // remove the ones that aren't in our acceptable set, if one is |
| // specified |
| if (availableNodes != null) { |
| nodes.retainAll(availableNodes); |
| } |
| // no name found, so no matches |
| if (nodes.size() == 0) { |
| return null; |
| } |
| // if the method has no params, then more than one mapping is pointless |
| Class<?>[] parameterTypes = method.getParameterTypes(); |
| if (parameterTypes.length == 0) { |
| return nodes.get(0); |
| } |
| // here's the fun part. |
| // we go through the method parameters, ruling out matches |
| for (int i = 0; i < parameterTypes.length; i++) { |
| Class<?> parameterType = parameterTypes[i]; |
| for (Iterator<Element> iterator = nodes.iterator(); iterator.hasNext();) { |
| Element element = iterator.next(); |
| // first we check if the parameter index is specified |
| Element match = getMatch(element, "parameter[@index='" + i + "']"); |
| if (match != null |
| // we check if the type is specified and matches |
| && DOMUtils.getAttributeValueEmptyNull(match, "class") != null |
| // if it doesn't match, then we can definitely rule out |
| // this result |
| && !DOMUtils.getAttributeValueEmptyNull(match, "class").equals(parameterType.getName())) { |
| |
| iterator.remove(); |
| } |
| } |
| } |
| // if we have just one node left, then it has to be the best match |
| if (nodes.size() == 1) { |
| return nodes.get(0); |
| } |
| // all remaining definitions could apply, so we need to now pick the |
| // best one |
| // the best one is the one with the most parameters specified |
| Element bestCandidate = null; |
| int highestSpecified = 0; |
| for (Iterator<Element> iterator = nodes.iterator(); iterator.hasNext();) { |
| Element element = iterator.next(); |
| |
| List<Element> params = DOMUtils.getChildrenWithName(element, "", "parameter"); |
| int availableParameters = params.size(); |
| if (availableParameters > highestSpecified) { |
| bestCandidate = element; |
| highestSpecified = availableParameters; |
| } |
| } |
| return bestCandidate; |
| } |
| |
| private Element getMatch(Node doc, String xpath) { |
| return (Element)getXPathUtils().getValue(xpath, doc, XPathConstants.NODE); |
| } |
| |
| private List<Element> getMatches(Node doc, String xpath) { |
| NodeList nl = (NodeList)getXPathUtils().getValue(xpath, doc, XPathConstants.NODESET); |
| List<Element> r = new ArrayList<Element>(); |
| for (int x = 0; x < nl.getLength(); x++) { |
| r.add((Element)nl.item(x)); |
| } |
| return r; |
| } |
| |
| /** |
| * Creates a QName from a string, such as "ns:Element". |
| */ |
| protected QName createQName(Element e, String value) { |
| if (value == null || value.length() == 0) { |
| return null; |
| } |
| |
| int index = value.indexOf(":"); |
| |
| if (index == -1) { |
| return new QName(getTypeMapping().getMappingIdentifierURI(), value); |
| } |
| |
| String prefix = value.substring(0, index); |
| String localName = value.substring(index + 1); |
| String ns = DOMUtils.getNamespace(e, prefix); |
| |
| if (ns == null || localName == null) { |
| throw new DatabindingException("Invalid QName in mapping: " + value); |
| } |
| |
| return new QName(ns, localName, prefix); |
| } |
| } |