org.apache.cxf.aegis.AegisContext Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cxf-rt-databinding-aegis Show documentation
Show all versions of cxf-rt-databinding-aegis Show documentation
Apache CXF Runtime Aegis Databinding
/**
* 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;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import org.w3c.dom.Document;
import org.apache.cxf.aegis.type.AbstractTypeCreator;
import org.apache.cxf.aegis.type.AegisType;
import org.apache.cxf.aegis.type.DefaultTypeCreator;
import org.apache.cxf.aegis.type.DefaultTypeMapping;
import org.apache.cxf.aegis.type.TypeCreationOptions;
import org.apache.cxf.aegis.type.TypeCreator;
import org.apache.cxf.aegis.type.TypeMapping;
import org.apache.cxf.aegis.type.TypeUtil;
import org.apache.cxf.aegis.type.XMLTypeCreator;
import org.apache.cxf.aegis.type.basic.BeanType;
import org.apache.cxf.aegis.type.java5.Java5TypeCreator;
import org.apache.cxf.common.classloader.ClassLoaderUtils;
import org.apache.cxf.common.xmlschema.XmlSchemaUtils;
import org.apache.cxf.staxutils.StaxUtils;
import org.apache.ws.commons.schema.XmlSchema;
import org.apache.ws.commons.schema.XmlSchemaCollection;
/**
* The Aegis Databinding context object. This object coordinates the data binding process: reading and writing
* XML. By default, this object sets up a default set of type mappings. This consists of two
* DefaultTypeMapping objects. The first is empty and has the Default, Java5, and XML TypeCreator classes
* configured. The second contains the standard mappings of the stock types. If a type can't be mapped in
* either, then the creators create a mapping and store it in the first one. The application can control some
* parameters of the type creators by creating a TypeCreationOptions object and setting properties. The
* application can add custom mappings to the type mapping, or even use its own classes for the TypeMapping or
* TypeCreator objects. Aegis, unlike JAXB, has no concept of a 'root element'. So, an application that uses
* Aegis without a web service has to either depend on xsi:type (at least for root elements) or have its own
* mapping from elements to classes, and pass the resulting Class objects to the readers. At this level, the
* application must specify the initial set of classes to make make use of untyped collections or .aegis.xml
* files. If the application leaves this list empty, and reads XML messages, then no .aegis.xml files are used
* unless the application has specified a Class<T> for the root of a particular item read. Specifically,
* if the application just leaves it to Aegis to map an element tagged with an xsi:type to a class, Aegis
* can't know that some arbitrary class in some arbitrary package is mapped to a particular schema type by
* QName in a mapping XML file. At the level of the CXF data binding, the 'root elements' are defined by the
* WSDL message parts. Additional classes that participate are termed 'override' classes.
*/
public class AegisContext {
/**
* Namespace used for the miscellaneous Aegis type schema.
*/
public static final String UTILITY_TYPES_SCHEMA_NS = "http://cxf.apache.org/aegisTypes";
private Document aegisTypesSchemaDocument;
private Document xmimeSchemaDocument;
private boolean writeXsiTypes;
private boolean readXsiTypes = true;
private Set rootClassNames;
private Set rootClasses;
private Set rootTypeQNames;
// this type mapping is the front of the chain of delegating type mappings.
private TypeMapping typeMapping;
private Set rootTypes;
private Map, String> beanImplementationMap;
private TypeCreationOptions configuration;
private boolean mtomEnabled;
private boolean mtomUseXmime;
private boolean enableJDOMMappings;
// this URI goes into the type map.
private String mappingNamespaceURI;
/**
* Construct a context.
*/
public AegisContext() {
beanImplementationMap = new HashMap<>();
rootClasses = new HashSet<>();
rootTypeQNames = new HashSet<>();
}
public TypeCreator createTypeCreator() {
AbstractTypeCreator xmlCreator = createRootTypeCreator();
Java5TypeCreator j5Creator = new Java5TypeCreator();
j5Creator.setNextCreator(createDefaultTypeCreator());
j5Creator.setConfiguration(getTypeCreationOptions());
xmlCreator.setNextCreator(j5Creator);
return xmlCreator;
}
protected AbstractTypeCreator createRootTypeCreator() {
AbstractTypeCreator creator = new XMLTypeCreator();
creator.setConfiguration(getTypeCreationOptions());
return creator;
}
protected AbstractTypeCreator createDefaultTypeCreator() {
AbstractTypeCreator creator = new DefaultTypeCreator();
creator.setConfiguration(getTypeCreationOptions());
return creator;
}
/**
* Initialize the context. The encodingStyleURI allows .aegis.xml files to have multiple mappings for,
* say, SOAP 1.1 versus SOAP 1.2. Passing null uses a default URI.
*/
public void initialize() {
// allow spring config of an alternative mapping.
if (configuration == null) {
configuration = new TypeCreationOptions();
}
if (typeMapping == null) {
boolean defaultNillable = configuration.isDefaultNillable();
TypeMapping baseTM = DefaultTypeMapping.createDefaultTypeMapping(defaultNillable,
mtomUseXmime,
enableJDOMMappings);
if (mappingNamespaceURI == null) {
mappingNamespaceURI = DefaultTypeMapping.DEFAULT_MAPPING_URI;
}
DefaultTypeMapping defaultTypeMapping = new DefaultTypeMapping(mappingNamespaceURI, baseTM);
defaultTypeMapping.setTypeCreator(createTypeCreator());
typeMapping = defaultTypeMapping;
}
processRootTypes();
}
public AegisReader createDomElementReader() {
return new AegisElementDataReader(this);
}
public AegisReader createXMLStreamReader() {
return new AegisXMLStreamDataReader(this);
}
public AegisWriter createDomElementWriter() {
return new AegisElementDataWriter(this);
}
public AegisWriter createXMLStreamWriter() {
return new AegisXMLStreamDataWriter(this);
}
/**
* If a class was provided as part of the 'root' list, retrieve it's AegisType by Class.
*
* @param clazz
* @return
*/
public AegisType getRootType(Class> clazz) {
if (rootClasses.contains(clazz)) {
return typeMapping.getType(clazz);
}
return null;
}
/**
* If a class was provided as part of the root list, retrieve it's AegisType by schema type QName.
*
* @param schemaTypeName
* @return
*/
public AegisType getRootType(QName schemaTypeName) {
if (rootTypeQNames.contains(schemaTypeName)) {
return typeMapping.getType(schemaTypeName);
}
return null;
}
private Set> rootMappableClasses() {
Set> mappableClasses = new HashSet<>();
for (java.lang.reflect.Type jtype : rootClasses) {
addTypeToMappableClasses(mappableClasses, jtype);
}
return mappableClasses;
}
private void addTypeToMappableClasses(Set> mappableClasses, java.lang.reflect.Type jtype) {
if (jtype instanceof Class) {
Class> jclass = (Class>) jtype;
if (jclass.isArray()) {
mappableClasses.add(jclass.getComponentType());
}
mappableClasses.add(jclass);
} else if (jtype instanceof ParameterizedType) {
for (java.lang.reflect.Type t2 : ((ParameterizedType)jtype).getActualTypeArguments()) {
addTypeToMappableClasses(mappableClasses, t2);
}
} else if (jtype instanceof GenericArrayType) {
GenericArrayType gt = (GenericArrayType)jtype;
Class> ct = (Class>) gt.getGenericComponentType();
// this looks nutty, but there's no other way. Make an array and take it's class.
ct = Array.newInstance(ct, 0).getClass();
rootClasses.add(ct);
}
}
/**
* Examine a list of override classes, and register all of them.
*/
private void processRootTypes() {
rootTypes = new HashSet<>();
// app may have already supplied classes.
if (rootClasses == null) {
rootClasses = new HashSet<>();
}
rootTypeQNames = new HashSet<>();
if (this.rootClassNames != null) {
for (String typeName : rootClassNames) {
try {
rootClasses.add(ClassLoaderUtils.loadClass(typeName, TypeUtil.class));
} catch (ClassNotFoundException e) {
throw new DatabindingException("Could not find override type class: " + typeName, e);
}
}
}
// This is a list of AegisType rather than Class so that it can set up for generic collections.
// When we see a generic, we process both the generic outer class and each parameter class.
// This is not the same thing as allowing mappings of arbitrary x types.
Set> rootMappableClassSet = rootMappableClasses();
/*
* First loop: process non-Class roots, creating full types for them
* and registering them.
*/
for (java.lang.reflect.Type reflectType : rootClasses) {
if (!(reflectType instanceof Class)) {
// if it's not a Class, it can't be mapped from Class to type in the mapping.
// so we create
AegisType aegisType = typeMapping.getTypeCreator().createType(reflectType);
typeMapping.register(aegisType);
// note: we don't handle arbitrary generics, so no BeanType
// check here.
rootTypeQNames.add(aegisType.getSchemaType());
}
}
/*
* Second loop: process Class roots, including those derived from
* generic types, creating when not in the default mappings.
*/
for (Class> c : rootMappableClassSet) {
AegisType t = typeMapping.getType(c);
if (t == null) {
t = typeMapping.getTypeCreator().createType(c);
typeMapping.register(t);
}
rootTypeQNames.add(t.getSchemaType());
if (t instanceof BeanType) {
BeanType bt = (BeanType)t;
bt.getTypeInfo().setExtension(true);
rootTypes.add(bt);
}
}
}
public static boolean schemaImportsUtilityTypes(XmlSchema schema) {
return XmlSchemaUtils.schemaImportsNamespace(schema, UTILITY_TYPES_SCHEMA_NS);
}
private Document getSchemaDocument(String resourcePath) {
try {
return StaxUtils.read(getClass().getResourceAsStream(resourcePath));
} catch (XMLStreamException e) {
throw new RuntimeException(e);
}
}
// could we make these documents static? What would we synchronize on?
private Document getAegisTypesSchemaDocument() {
if (aegisTypesSchemaDocument == null) {
aegisTypesSchemaDocument = getSchemaDocument("/META-INF/cxf/aegisTypes.xsd");
}
return aegisTypesSchemaDocument;
}
private Document getXmimeSchemaDocument() {
if (xmimeSchemaDocument == null) {
xmimeSchemaDocument = getSchemaDocument("/schemas/wsdl/xmime.xsd");
}
return xmimeSchemaDocument;
}
public XmlSchema addTypesSchemaDocument(XmlSchemaCollection collection) {
return collection.read(getAegisTypesSchemaDocument(), null);
}
public XmlSchema addXmimeSchemaDocument(XmlSchemaCollection collection) {
return collection.read(getXmimeSchemaDocument(), null);
}
public static void addUtilityTypesToSchema(XmlSchema root) {
XmlSchemaUtils.addImportIfNeeded(root, UTILITY_TYPES_SCHEMA_NS);
}
/**
* Retrieve the set of root class names. Note that if the application specifies the root classes by Class
* instead of by name, this will return null.
*
* @return
*/
public Set getRootClassNames() {
return rootClassNames;
}
/**
* Set the root class names. This function is a convenience for Spring configuration. It sets the same
* underlying collection as {@link #setRootClasses(Set)}.
*
* @param classNames
*/
public void setRootClassNames(Set classNames) {
rootClassNames = classNames;
}
/**
* Return the type mapping configuration associated with this context.
*
* @return Returns the configuration.
*/
public TypeCreationOptions getTypeCreationOptions() {
return configuration;
}
/**
* Set the configuration object. The configuration specifies default type mapping behaviors.
*
* @param newConfiguration The configuration to set.
*/
public void setTypeCreationOptions(TypeCreationOptions newConfiguration) {
this.configuration = newConfiguration;
}
public boolean isWriteXsiTypes() {
return writeXsiTypes;
}
public boolean isReadXsiTypes() {
return readXsiTypes;
}
/**
* Controls whether Aegis writes xsi:type attributes on all elements. False by default.
*
* @param flag
*/
public void setWriteXsiTypes(boolean flag) {
this.writeXsiTypes = flag;
}
/**
* Controls the use of xsi:type attributes when reading objects. By default, xsi:type reading is enabled.
* When disabled, Aegis will only map for objects that the application manually maps in the type mapping.
*
* @param flag
*/
public void setReadXsiTypes(boolean flag) {
this.readXsiTypes = flag;
}
/**
* Return the type mapping object used by this context.
*
* @return
*/
public TypeMapping getTypeMapping() {
return typeMapping;
}
/**
* Set the type mapping object used by this context.
*
* @param typeMapping
*/
public void setTypeMapping(TypeMapping typeMapping) {
this.typeMapping = typeMapping;
}
/**
* Retrieve the Aegis type objects for the root classes.
*
* @return the set of type objects.
*/
public Set getRootTypes() {
return rootTypes;
}
/**
* This property provides support for interfaces. If there is a mapping from an interface's Class to a
* string containing a class name, Aegis will create proxy objects of that class name.
*
* @see org.apache.cxf.aegis.type.basic.BeanType
* @return
*/
public Map, String> getBeanImplementationMap() {
return beanImplementationMap;
}
public void setBeanImplementationMap(Map, String> beanImplementationMap) {
this.beanImplementationMap = beanImplementationMap;
}
public Set getRootClasses() {
return rootClasses;
}
/**
* The list of initial classes.
*
* @param rootClasses
*/
public void setRootClasses(Set rootClasses) {
this.rootClasses = rootClasses;
}
/**
* Is MTOM enabled in this context?
*
* @return
*/
public boolean isMtomEnabled() {
return mtomEnabled;
}
public void setMtomEnabled(boolean mtomEnabled) {
this.mtomEnabled = mtomEnabled;
}
/**
* Should this service use schema for MTOM types xmime:base64Binary instead of xsd:base64Binary?
*
* @return
*/
public boolean isMtomUseXmime() {
return mtomUseXmime;
}
public void setMtomUseXmime(boolean mtomUseXmime) {
this.mtomUseXmime = mtomUseXmime;
}
/**
* What URI identifies the type mapping for this context? When the XMLTypeCreator reads .aegis.xml file,
* it will only read mappings for this URI (or no URI). When the abstract type creator is otherwise at a
* loss for a namespace URI, it will use this URI.
*
* @return
*/
public String getMappingNamespaceURI() {
return mappingNamespaceURI;
}
public void setMappingNamespaceURI(String mappingNamespaceURI) {
this.mappingNamespaceURI = mappingNamespaceURI;
if (typeMapping != null) {
typeMapping.setMappingIdentifierURI(mappingNamespaceURI);
}
}
public boolean isEnableJDOMMappings() {
return enableJDOMMappings;
}
/**
* Whether to enable JDOM as a mapping for xsd:anyType if JDOM is in the classpath.
* @param enableJDOMMappings
*/
public void setEnableJDOMMappings(boolean enableJDOMMappings) {
this.enableJDOMMappings = enableJDOMMappings;
}
}