1. Overview

This tutorial gives an introduction to Aegis data binding, a subsystem that can map between Java objects and XML documents described by XML schemas. Aegis allows detailed control over the mapping process while keeping programming effort to a minimum.

Aegis is part of Apache CXF, but not constrained to be used within this framework only. Instead, this data binding mechanism may be used anywhere and hence in this tutorial we focus on its usage as an independent subsystem.

2. Maven Dependencies

The only dependency required to activate Aegis data binding is:

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-databinding-aegis</artifactId>
    <version>3.1.8</version>
</dependency>

The latest version of this artifact can be found here.

3. Type Definitions

This section walks through the definitions of three types used to illustrate Aegis.

3.1. Course

This is the simplest class in our example, which is defined as:

public class Course {
    private int id;
    private String name;
    private String instructor;
    private Date enrolmentDate;

    // standard getters and setters
}

3.2. CourseRepo

CourseRepo is the top-level type in our model. We define it as an interface rather than a class to demonstrate how easy it is to marshal a Java interface, which is impossible in JAXB without a custom adapter:

public interface CourseRepo {
    String getGreeting();
    void setGreeting(String greeting);
    Map<Integer, Course> getCourses();
    void setCourses(Map<Integer, Course> courses);
    void addCourse(Course course);  
}

Note that we declare the getCourses method with the return type Map. This is intentional to express another advantage of Aegis over JAXB. The latter cannot marshal a map without a custom adapter while the former can.

3.3. CourseRepoImpl

This class provides implementation to the CourseRepo interface:

public class CourseRepoImpl implements CourseRepo {
    private String greeting;
    private Map<Integer, Course> courses = new HashMap<>();

    // standard getters and setters

    @Override
    public void addCourse(Course course) {
        courses.put(course.getId(), course);
    }
}

4. Custom Data Binding

In order for the customization to take effect, XML mapping files must be present on the classpath. It is required that those files are placed into a directory whose structure corresponds to the package hierarchy of the associated Java types.

For example, if a fully qualified name class is named package.ClassName, its associated mapping file must be inside the package/ClassName subdirectory on the classpath. The name of a mapping file must be equal to the associated Java type with a .aegis.xml suffix appended to it.

4.1. CourseRepo Mapping

The CourseRepo interface belongs to the com.baeldung.cxf.aegis package, so its corresponding mapping file is named CourseRepo.aegis.xml and put into the com/baeldung/cxf/aegis directory on the classpath.

In the CourseRepo mapping file, we change name and namespace of the XML element associated with the CourseRepo interface, as well as the style of its greeting property:

<mappings xmlns:ns="http://courserepo.baeldung.com">
    <mapping name="ns:Baeldung">
        <property name="greeting" style="attribute"/>
    </mapping>
</mappings>

4.2. Course Mapping

Similar to the CourseRepo type, the mapping file of class Course is named Course.aegis.xml and located in the com/baeldung/cxf/aegis directory as well.

In this mapping file, we instruct Aegis to ignore the instructor property of the Course class when marshaling, so that its value is not available in the object recreated from the output XML document:

<mappings>
    <mapping>
        <property name="instructor" ignore="true"/>
    </mapping>
</mappings>

Aegis’ home page is where we can find more customization options.

5. Testing

This section is a step-by-step guide to set up and execute a test case that illustrates the usage of Aegis data bindings.

To facilitate the testing process, we declare two fields within the test class:

public class BaeldungTest {
    private AegisContext context;
    private String fileName = "baeldung.xml";

    // other methods
}

These fields have been defined here to be used by other methods of this class.

5.1. AegisContext Initialization

First, an AegisContext object must be created:

context = new AegisContext();

That AegisContext instance is then configured and initialized. Here is how we set root classes for the context:

Set<Type> rootClasses = new HashSet<Type>();
rootClasses.add(CourseRepo.class);
context.setRootClasses(rootClasses);

Aegis creates an XML mapping element for each Type within the Set object. In this tutorial, we set only CourseRepo as a root type.

Now, let’s set the implementation map for the context to specify the proxy class for the CourseRepo interface:

Map<Class<?>, String> beanImplementationMap = new HashMap<>();
beanImplementationMap.put(CourseRepoImpl.class, "CourseRepo");
context.setBeanImplementationMap(beanImplementationMap);

The last configuration for the Aegis context is telling it to set the xsi:type attribute in the corresponding XML document. This attribute carries the actual type name of the associated Java object unless overridden by the mapping file:

context.setWriteXsiTypes(true);

Our AegisContext instance is now ready to be initialized:

context.initialize();

To keep the code clean, we collect all code snippets from this subsection into one helper method:

private void initializeContext() {
    // ...
}

5.2. Simple Data Setup

Due to the simple nature of this tutorial, we generate sample data right in memory rather than relying on a persistent solution. Let’s populate the course repo using the setup logic below:

private CourseRepoImpl initCourseRepo() {
    Course restCourse = new Course();
    restCourse.setId(1);
    restCourse.setName("REST with Spring");
    restCourse.setInstructor("Eugen");
    restCourse.setEnrolmentDate(new Date(1234567890000L));
    
    Course securityCourse = new Course();
    securityCourse.setId(2);
    securityCourse.setName("Learn Spring Security");
    securityCourse.setInstructor("Eugen");
    securityCourse.setEnrolmentDate(new Date(1456789000000L));
    
    CourseRepoImpl courseRepo = new CourseRepoImpl();
    courseRepo.setGreeting("Welcome to Beldung!");
    courseRepo.addCourse(restCourse);
    courseRepo.addCourse(securityCourse);
    return courseRepo;
}

5.3. Binding Java Objects and XML Elements

The steps that need to be taken to marshal Java objects to XML elements are illustrated with the following helper method:

private void marshalCourseRepo(CourseRepo courseRepo) throws Exception {
    AegisWriter<XMLStreamWriter> writer = context.createXMLStreamWriter();
    AegisType aegisType = context.getTypeMapping().getType(CourseRepo.class);
    XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance()
      .createXMLStreamWriter(new FileOutputStream(fileName));
    
    writer.write(courseRepo, 
      new QName("http://aegis.cxf.baeldung.com", "baeldung"), false, xmlWriter, aegisType);
    
    xmlWriter.close();
}

As we can see, the AegisWriter and AegisType objects must be created from the AegisContext instance. The AegisWriter object then marshals the given Java instance to the specified output.

In this case, this is an XMLStreamWriter object associated with a file named after the value of the fileName class-level field in the file system.

The following method unmarshals an XML document to a Java object of the given type:

private CourseRepo unmarshalCourseRepo() throws Exception {       
    AegisReader<XMLStreamReader> reader = context.createXMLStreamReader();
    XMLStreamReader xmlReader = XMLInputFactory.newInstance()
      .createXMLStreamReader(new FileInputStream(fileName));
    
    CourseRepo courseRepo = (CourseRepo) reader.read(
      xmlReader, context.getTypeMapping().getType(CourseRepo.class));
    
    xmlReader.close();
    return courseRepo;
}

Here, an AegisReader object is generated from the AegisContext instance. The AegisReader object then creates a Java object out of the provided input. In this example, that input is an XMLStreamReader object backed by the file we generated in the marshalCourseRepo method described right above.

5.4. Assertions

Now, it is time to combine all helper methods defined in previous subsections into a test method:

@Test
public void whenMarshalingAndUnmarshalingCourseRepo_thenCorrect()
  throws Exception {
    initializeContext();
    CourseRepo inputRepo = initCourseRepo();
    marshalCourseRepo(inputRepo);
    CourseRepo outputRepo = unmarshalCourseRepo();
    Course restCourse = outputRepo.getCourses().get(1);
    Course securityCourse = outputRepo.getCourses().get(2);

    // JUnit assertions
}

We first create a CourseRepo instance, then marshal it to an XML document and finally unmarshal the document to recreate the original object. Let’s verify that the recreated object is what we expect:

assertEquals("Welcome to Beldung!", outputRepo.getGreeting());
assertEquals("REST with Spring", restCourse.getName());
assertEquals(new Date(1234567890000L), restCourse.getEnrolmentDate());
assertNull(restCourse.getInstructor());
assertEquals("Learn Spring Security", securityCourse.getName());
assertEquals(new Date(1456789000000L), securityCourse.getEnrolmentDate());
assertNull(securityCourse.getInstructor());

It is clear that except for the instructor property, all other ones have their values recovered, including the enrolmentDate property with values of type Date. This is exactly what we expect as we have instructed Aegis to ignore the instructor property when marshaling Course objects.

5.5. Output XML Document

To make the effect of Aegis mapping files explicit, we show the XML document without customization below:

<ns1:baeldung xmlns:ns1="http://aegis.cxf.baeldung.com"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:type="ns1:CourseRepo">
    <ns1:courses>
        <ns2:entry xmlns:ns2="urn:org.apache.cxf.aegis.types">
            <ns2:key>1</ns2:key>
            <ns2:value xsi:type="ns1:Course">
                <ns1:enrolmentDate>2009-02-14T06:31:30+07:00
                </ns1:enrolmentDate>
                <ns1:id>1</ns1:id>
                <ns1:instructor>Eugen</ns1:instructor>
                <ns1:name>REST with Spring</ns1:name>
            </ns2:value>
        </ns2:entry>
        <ns2:entry xmlns:ns2="urn:org.apache.cxf.aegis.types">
            <ns2:key>2</ns2:key>
            <ns2:value xsi:type="ns1:Course">
                <ns1:enrolmentDate>2016-03-01T06:36:40+07:00
                </ns1:enrolmentDate>
                <ns1:id>2</ns1:id>
                <ns1:instructor>Eugen</ns1:instructor>
                <ns1:name>Learn Spring Security</ns1:name>
            </ns2:value>
        </ns2:entry>
    </ns1:courses>
    <ns1:greeting>Welcome to Beldung!</ns1:greeting>
</ns1:baeldung>

Compare this with the case when Aegis custom mapping is in action:

<ns1:baeldung xmlns:ns1="http://aegis.cxf.baeldung.com"
    xmlns:ns="http://courserepo.baeldung.com"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:type="ns:Baeldung" greeting="Welcome to Beldung!">
    <ns:courses>
        <ns2:entry xmlns:ns2="urn:org.apache.cxf.aegis.types">
            <ns2:key>1</ns2:key>
            <ns2:value xsi:type="ns1:Course">
                <ns1:enrolmentDate>2009-02-14T06:31:30+07:00
                </ns1:enrolmentDate>
                <ns1:id>1</ns1:id>
                <ns1:name>REST with Spring</ns1:name>
            </ns2:value>
        </ns2:entry>
        <ns2:entry xmlns:ns2="urn:org.apache.cxf.aegis.types">
            <ns2:key>2</ns2:key>
            <ns2:value xsi:type="ns1:Course">
                <ns1:enrolmentDate>2016-03-01T06:36:40+07:00
                </ns1:enrolmentDate>
                <ns1:id>2</ns1:id>
                <ns1:name>Learn Spring Security</ns1:name>
            </ns2:value>
        </ns2:entry>
    </ns:courses>
</ns1:baeldung>

You may find this XML structure in the baeldung.xml, right inside the project’s main directory after running the test defined in this section.

You will see that the type attribute and namespace of the XML element corresponding to the CourseRepo object change in accordance with what we set in the CourseRepo.aegis.xml file. The greeting property is also transformed into an attribute, and the instructor property of Course objects disappears as expected.

It is worth to note that by default Aegis converts a basic Java type to the best matching schema type, e.g. from Date objects to xsd:dateTime elements as shown in this tutorial. However, we can change that particular binding by setting the configuration in the corresponding mapping file.

Please navigate to the Aegis home page if you want to have more information.

6. Conclusion

This tutorial illustrates the use of the Apache CXF Aegis data binding as a standalone subsystem. It demonstrates how Aegis may be used to map Java objects to XML elements, and vice versa.

The tutorial also focuses on how to customize data binding behaviors.

And, as always, the implementation of all these examples and code snippets can be found in the GitHub project.