For CXF 2.1 or newer
What is Aegis?
Aegis is a databinding. That is, it is a subsystem that can map Java objects to XML documents described by XML schema, and vica-versa. Aegis is designed to give useful mappings with a minimum of programmer effort, while allowing detailed control and customization.
Aegis began as part of XFire, and moved with XFire into Apache CXF.
You can use Aegis independently of CXF as a mechanism for mapping Java objects to and from XML. This page, however, describes Aegis as used inside of CXF.
Aegis has some advantages over JAXB for some applications. Some users find that it produces a more natural XML mapping for less configuration. For example, Aegis has a default setting for 'nillable', allowing you to declare it for your entire service in one place instead of having to annotate every single element. The biggest advantage of Aegis, however, is a convenient way to customize the mapping without adding (@)annotations to your Java code. This allows you to avoid class loading dependencies between your data classes and your web service binding.
Getting Started: Basic Use of Aegis
You can configure any web service to use the Aegis data binding. A service configured with Aegis will yield a valid WSDL description, and you can use that to configure any client that you like. You can talk to an Aegis service with JAXB, or .NET, or a scripted language, or ... Aegis itself.
You can use Aegis as a client to talk to Aegis, by using the very same Java classes and configuration files in the client environment that you use on the server. However, it's not all that practical to use Aegis as a client to talk to some a service using some other data binding, since Aegis lacks a 'wsdl2java' tool.
Using Aegis on the client side also carries severe risks of compatibility problems. Since there is no WSDL to specify the contract, small changes in your code or in CXF can result in a situation where the client and the server are incompatible. If you want to use Aegis on the client side, you should be sure to use exactly the same version of CXF on both sides. If you cannot do that, you should consider generating JAX-WS/JAX-B code for the client using wsdl2java.
Every CXF service and client uses a front end: JAX-WS, Simple, etc. Each of these provides a place to configure the data binding, both in Spring and via Java code.
For example, here is a Simple front-end service using Aegis as a data binding.
<simple:server id="pojoservice" serviceClass="demo.hw.server.HelloWorld"
address="/hello_world">
<simple:serviceBean>
<bean class="demo.hw.server.HelloWorldImpl" />
</simple:serviceBean>
<simple:dataBinding>
<bean class="org.apache.cxf.aegis.databinding.AegisDatabinding" />
</simple:dataBinding>
</simple:server>
AegisDatabinding is the class that integrates Aegis into CXF as a databinding.
Note that AegisDatabinding beans, like all databinding beans, are not reusable. The example above uses an anonymous nested bean for the databinding. If you make a first-class bean for a databinding, be sure to use scope='prototype' if you are inclined to define more than one endpoint.
Aegis Operations - The Simple Case
How does Aegis work? Aegis maintains, for each service, a set of mappings from Java types (Class<?> objects) to XML Schema types. It uses that mapping to read and write XML. Let's look at a simple service, where all the Java types involved are either Java built-in types, other types with predefined mappings to XML Schema, or simple bean-pattern classes that have properties that (recursively) are simple.
Let's start with serializing: mapping from Java to XML. (JAXB calls this marshalling, and cannot decide how many 'l's to use in spelling it.) Given a Java object, Aegis looks to see if it has a mapping. By default, Aegis has a set of default mappings for the basic types defined in XML Schema, plus a few other special items. These mappings are implemented by Java classes, parts of Aegis, that can turn objects in to XML and visa versa. In particular, note that Aegis will map a DataSource or DataHandler to an MTOM attachment.
What if Aegis finds no mapping for a type? In the default configuration, Aegis invokes the type creators to create a mapping. Type creators use several mechanisms to create XML schema from Java objects. This include reflection, annotations, and XML type mappings files. As part of the mapping process, Aegis will assign a namespace URI based on the Java package. (Note: Aegis does not support elementForm='unqualified' at this time.) These mappings are implemented by a generic mapping class, and stored away.
How about the reverse process: deserializing? (JAXB calls this unmarshalling.) In this case, by default, Aegis is presented with an XML element and asked to produce a Java object. Recall, however, that the Aegis maintains a mapping from Java types to XML Schema Types. By default, an XML instance document offers no information as to the type of a given element. How can Aegis determine the Java type? Outside of CXF, the application would have to tell Aegis the expected type for the root element of a document.
Or, as an alternative, Aegis can add xsi:type attributes to top-level elements when writing. It will always respect them when reading.
Inside CXF, Aegis gets the benefit of the Message and Part information for the service. The WSDL service configuration for a service gives enough information to associate an XML Schema type with each part. Once the front-end has determined the part, it can call Aegis with the QName for the schema type, and Aegis can look it up in the mapping.
Will it be in the mapping? Yes, inside CXF because Aegis precreates mappings for the types in the service's parts. Aegis cannot dynamically create or choose a Java class based on XML schema, so the type creators cannot start from XML. Thus, outside CXF you are responsible for ensuring that your top-level types are mapped.
Schema Validation
As of CXF 2.3, the Aegis databinding can leverage the Schema Validation capabilities built into the Woodstox 4.x Stax parser to validate incoming requests. To enable this, you must do the following:
- Make sure you are using the Woodstox 4.x Stax parser and not a 3.x or other parser. By default, CXF 2.3 ships with an appropriate version of Woodstox.
- If not using the CXF bundle jar, (example, if using maven), you'll need to add the cxf-wstx-msv-validation-2.3.0.jar to the classpath
- If not using maven or similar to obtain the cxf-wstx-msv-validation jar, you'll also need to add the msv validation jars as CXF does not ship them by default. You will need:
isorelax-20030108.jar
msv-core-2009.1.jar
relaxngDatatype-20020414.jar
xercesImpl-2.9.1.jar
xml-resolver-1.2.jar
xsdlib-2009.1.jar
- If not using a default bus (such as configuring your own spring context), you'll need to add:
<import resource="classpath:META-INF/cxf/cxf-extension-wstx-msv-validation.xml" />
to load the validator utilities that Aegis will use. - Turn on schema validation like you would for JAXB by using the @SchemaValidation annotation or setting the "schema-validation-enabled" property on the endpoint to "true".
Using Java Classes That Aren't Visible to the Service Interface
Many web service programmers want to use types that are not directly visible by reflection of the service interface. Here are some popular examples of types that programmers want to use for property or parameter types:
- Declare a base type, but transfer any one of a number of classes that extend it.
- Declare a raw Collection class, such as a Set, List, or Map, and send arbitrary objects as keys and values.
- Declare a base exception type for 'throws', and then throw other exception classes that derive from it.
- Declare an interface or an abstract type.
Aegis can handle all of these. For all except interfaces, there are two mechanisms involved: the root class list and xsi:type attributes.
As explained above, Aegis can write 'anything', but it can only read objects of types that are mapped. You must give Aegis a list of all the types that you want to use over and above those visible from the service, and you must instruct Aegis to send xsi:type attributes when sending
objects of such types.
These type attributes allow Aegis to identify the type of these additional objects and look them up in the mappings.
Interfaces and abstract types require one further step. Obviously, Aegis cannot instantiate (run 'new') on an interface. Thus, knowing that a particular XML Schema type maps to an interface is not enough information. To be able to read an XML element that corresponds to an interface, Aegis must know a 'proxy class' that implements the interface. You must give Aegis a mapping from interface types to proxy class names.
How does this work? The core of Aegis is the AegisContext class. Each AegisDatabinding object has an AegisContext. (It is probably not possible to share an AegisContext amongst databindings.)
By default, AegisDatabinding will create its own AegisContext with default properties. To configure additional types, as well control other options that we will examine later on, you must create the AegisContext for yourself and specify some of its properties. Then you pass your AegisContext object into your AegisDatabinding object.
To use additional classes or interfaces, you need to set two (or three) properties of your AegisContext.
- rootClasses is a collection of Java Class<?> objects. These are added to the list of types known to Aegis. Aegis will create a mapping for each. For convenience, there is a rootClassNames property for use from Spring. It is a list of Strings containing class names.
- writeXsiTypes is a boolean. Set it to true to send xsi:type attributes.
- beanImplementationMap is a mapping from Class<?> to class names. Use this to specify proxy classes for interfaces (or abstract classes).
Global Type Creation Options
There are a few global options to the default type mapping process. You can control these by creating a org.apache.cxf.aegis.type.TypeCreationOptions and passing it into your AegisContext object.
There are four properties in the class, of which two are much more commonly used.
- defaultNillable defines the default value of the nillable attribute of xsd:element items in the xsd:sequences built for non-primitive types. By default, it is true, since any Java reference can be null. However, nillable='true' has annoying consequences in some wsdl2java tools (turning scalars into arrays, e.g.), and so many programmers prefer to default to false.
- defaultMinOccurs defines the default value of the minOccurs attribute of xsd:element items in the xsd:sequences built for Java arrays. In combination with nillable, programmers often want to adjust this value from 0 to 1 to get a more useful mapping of an array.
- defaultExtensibleElements causes each sequence to end with an xsd:any. The idea here is to allow for schema evolution; a client that has generated Java from one version of the service will tolerate data from a newer version that has additional elements. Use this feature with care; version management of web services is a complex topic, and xsd:any may have unexpected consequences.
- defaultExtensibleAttributes causes each element to permit any attribute. By default, Aegis doesn't map any properties or parameters to attributes. As with the element case, care is called for.
Note that these are options to the default type creators. If you take the step of creating a customized type creator, it will be up to you to respect or ignore these options.
Here's a quick example of Java code setting these options. In Spring you would do something analogous with properties.
TypeCreationOptions tOpts = new TypeCreationOptions();
tOpts.setDefaultMinOccurs(1);
tOpts.setDefaultNillable(false);
AegisDatabinding aDB = new AegisDatabinding();
aDB.getAegisContext().setTypeCreationOptions(tOpts);
sFactory.getServiceFactory().setDataBinding(aDB);
Detailed Control of Bean Type Mapping
This page has descended, gradually, from depending on Aegis' defaults toward exercising more detailed control over the process. The next level of detail is to customize the default type creators' behavior via XML mapping files and annotations.
XML Mapping Files
XML mapping files are a major distinguishing feature of Aegis. They allow you to specify details of the mapping process without either (a) modifying your Java source for your types or (b) maintaining a central file of some kind containing mapping instructions.
Aegis XML mapping applies to services and to beans. By "beans," we mean "Java classes that follow the bean pattern, used in a web service." "Services," you ask? Aren't they the responsibility of the CXF front end? There is some overlap in the responsibilities of front-ends and databindings.
By and large, front-ends map services to XML schema, filling in XML Schema elements and types for messages and parts. Data bindings then map from those schema items to Java. However, Aegis also provides XML configuration for methods and parameters, which 'poach' in the territory of the front end. This works well for the Simple front end, which has no other way to control these mappings. The present author is not sure what will happen in the event of a conflict between Aegis and any other front-end, like JAX-WS, that has explicit configuration. Thus, Aegis service configuration is best used with the Simple front end.
For both bean and service customization, Aegis looks for customization in files found by the classloader. If your class is my.hovercraft.is.full.of.Eels, Aegis will search the classpath for /my/hovercraft/is/full/of/Eels.aegis.xml. In other words, if Eels.class is sitting in a JAR file or a directory, Eels.aegis.xml can be sitting right next to it.
Or, on the other hand, it can be in a completely different JAR or tree, so long as it ends up in the same logical location. In other words, you can create XML files for classes when you don't even have their source.
This is a copy of the XML Schema for mapping XML files that is annotated with comments.
Bean Mapping
Here is a very simple mapping. It takes a property named 'horse', renames it to 'feathers', and makes it an attribute instead of an element.
<mappings>
<mapping name="">
<property name="horse" mappedName="Feathers" style="attribute"/>
</mapping>
</mappings>
Names and Namespaces
You can also specify the full QName of the bean itself. The following mapping causes a class to have the QName {urn:north-pole:operations}Employee.
<mappings xmlns:np="urn:north-pole:operations">
<mapping name="np:Employee">
</mapping>
</mappings>
Notice that the namespace was declared on the mappings element and then the prefix was used to specify the element QNames for the name/title properties.
This will result in a mapping like so:
<np:Employee xmlns:np="urn:north-pole:operations">
<np:Name>Santa Claus</np:Name>
<np:Title>Chief Present Officer (CPO)</np:Title>
</np:Employee>
Ignoring properties
If you don't want to serialize a certain property it is easy to ignore it:
<mappings>
<mapping>
<property name="propertyName" ignore="true"/>
</mapping>
</mappings>
MinOccurs and Nillable
The default Aegis mapping is to assume that, since any Java object can be null, that the corresponding schema elements should have minOccurs of 0 and nillable of true. There are properties on the mappings for to control this.
<mappings>
<mapping>
<property name='everpresentProperty' minOccurs='1' nillable='false'/>
</mapping>
<mappings>
Alternative Type Binding
Later on, we will explain how to replace the default mappings that Aegis provides for basic types. However, there are some cases where you may want to simply specify one of the provided type mappings for one of your properties. You can do that from the XML mapping file without creating any Java customization.
By default, for example, if Aegis maps a property as a Date, it uses the XML schema type xsd:dateTime. Here is an example that uses xsd:date, instead.
<mappings xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<mapping>
<property name="birthDate"
type="org.apache.cxf.aegis.type.basic.DateType"
typeName="xsd:date"
/>
</mapping>
</mappings>
Collections
If you use a 'raw' collection type, Aegis will map it as a collection of xsd:any particles. If you want the WSDL to show it as a collection of some specific type, the easiest thing to do is to use Java generics instead of raw types. If, for some reason, you can't do that, you can use the componentType and keyType attributes of a property to specify the Java classes.
Multiple mappings for Different Services
What if you want to specify different mapping behavior for different services on the same types? The 'mapping' element of the file accepts a 'uri' attribute. Each AegisContext has a 'mappingNamespaceURI' attribute. If a mapping in a .aegis.xml file has a uri attribute, it must match the current service's uri.
Services and Parameters
For a service, mapping files specify attributes of operations and parameters.
This example specifies that getUnannotatedStrings returns an element named UnannotatedStringCollection which is a raw collection of String values. It then specifies the first parameter of getValues is also a raw collection of String values.
<mappings>
<mapping>
<method name="getUnannotatedStrings">
<return-type name="UnannotatedStringCollection"
componentType="java.lang.String"/>
</method>
<method name="getValues">
<parameter index="0" componentType="java.lang.String"/>
</method>
</mapping>
</mappings>
Annotations
Like JAXB, Aegis supports some Java annotations to control the mapping process. These attributes are modelled after JAXB. Aegis defines them in the package org.apache.cxf.aegis.type.java5. They are:
- XmlAttribute
- XmlType
- XmlElement
- XmlReturnType
- XmlIgnore
In addition, Aegis will respect actual JAXB annotations from the following list:
- javax.jws.WebParam
- javax.jws.WebResult
- javax.xml.bind.annotation.XmlAttribute
- javax.xml.bind.annotation.XmlElement
- javax.xml.bind.annotation.XmlSchema
- javax.xml.bind.annotation.XmlType
- javax.xml.bind.annotation.XmlTransient
Note, however, that Aegis does not handle package-info.java classes, and so XmlSchema must be applied to a class.
Creating Your Own Type Mappings
If you want complete control on the mapping between Java and XML, you must create your own type mappings. To do this, you should make a class that extends org.apache.cxf.aegis.type.Type, and then you must register it in a type mapping for your service.
To see how these classes work, read the source code.
To register your type mappings, you have two choices.
If you just want to add a custom type mapping into your service, the easiest thing to do is to retrieve the TypeMapping from the AegisContext, and register your type as a mapping from Class<?> to your custom mapping object.
If you want complete control over the process, you can create your own TypeMapping. The class DefaultTypeMapping is the standard type map. You can use these, or you can create your own implementation of TypeMapping. Set up your type mapping as you like, and install it in your context before the service is initialized.
Customizing Type Creation
What if you want to change how Aegis builds new type mappings and types from Java classes? You can create your own TypeCreator, and either put it in the front of the list of type creators or replace the entire standard list.
As with type mappings, reading the source is the only way to learn the details. Type creators are associated with type mappings; you can call setTypeCreator on an instance of DefaultTypeMapping to install yours.