|
Apache CXF example source code file (XMLTypeCreator.java)
This example Apache CXF source code file (XMLTypeCreator.java) is included in the DevDaily.com
"Java Source Code
Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.
The Apache CXF XMLTypeCreator.java source code
/**
* 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.
* 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();
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();
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) {
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
try {
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 XPathUtils xpathUtils = new 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;
}
Document doc = documents.get(clazz.getName());
if (doc != null) {
return doc;
}
String path = '/' + clazz.getName().replace('.', '/') + ".aegis.xml";
InputStream is = clazz.getResourceAsStream(path);
if (is == null) {
LOG.finest("Mapping file : " + path + " not found.");
return null;
}
LOG.finest("Found mapping file : " + path);
try {
doc = readAegisFile(is, path);
documents.put(clazz.getName(), doc);
return doc;
} catch (IOException e) {
LOG.log(Level.SEVERE, "Error loading file " + path, e);
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();
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.
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 iterator = nodes.iterator(); iterator.hasNext();) {
Element 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 iterator = nodes.iterator(); iterator.hasNext();) {
Element 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)xpathUtils.getValue(xpath, doc, XPathConstants.NODE);
}
private List<Element> getMatches(Node doc, String xpath) {
NodeList nl = (NodeList)xpathUtils.getValue(xpath, doc, XPathConstants.NODESET);
List<Element> r = new ArrayList();
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);
}
}
Other Apache CXF examples (source code examples)
Here is a short list of links related to this Apache CXF XMLTypeCreator.java source code file:
|