This article explains the LuciadLightspeed XML framework.

Introduction to XML

The XML binding framework allows you to convert XML documents into Java content trees and the other way around. The framework is introduced to support other LuciadLightspeed XML-based formats, such as GML (discussed in Integrating GML data into your application), AIXM and OGC Filter. The com.luciad.format.xml.bind, com.luciad.format.xml.bind.schema, and format.xml.bind.schema.dataobject are the main packages.

The new framework replaces the deprecated format.xml framework. The new framework is more efficient and easier to use and also provides additional functionality.

The current LuciadLightspeed implementation is based on the XML 1.0 specification, which you can find at W3C’s website (http://www.w3.org/TR/REC-xml/). Reading and writing XML data is done using the streaming API for XML (StAX). This article is not meant to be an XML course; it assumes the reader has a thorough understanding of XML and XML schema, and at least a basic knowledge of StAX.

Creating a Java content tree from an XML document is called unmarshalling, the other way around is called marshalling. In this article, these terms are used interchangeably with decoding and encoding. Conceptually encoding and decoding are treated in a very similar fashion implemented with parallel classes (typically for each encoding class there is a similar decoding class). Because of that, the focus of the rest of this article is only on decoding.

This article focuses on documents based on a XML schema as all supported XML-based formats in LuciadLightspeed are based on a schema. The framework can also handle documents without a schema. The API Reference of the package com.luciad.format.xml.bind contains more information about this.

The XML binding framework is tightly integrated with the unified domain object approach described in Unified object access. This article assumes you have a good understanding of that article.

Representing schema information

Because an XML schema is such an important aspect of most standardized XML formats, LuciadLightspeed introduces dedicated classes to represent information about a schema.

Decoding XML documents

Use a TLcdXMLSchemaBasedDecoder to decode schema-based XML documents. In order to create the right domain objects, you need to configure the decoder so that it knows the structure of the document and how to map it on the Java domain object classes. For data models that are based on an XML schema, this is straightforward. Program: Decoding ISO19115 XML documents shows this for the ISO19115 data model.

You can find a more detailed explanation of ISO 19115 in Working with ISO metadata.

First a decoder is created and then it is configured for the ISO19115 data model. This makes the decoder ready to decode ISO 19115 documents.

Program: Decoding ISO19115 XML documents
TLcdXMLSchemaBasedDecoder decoder = new TLcdXMLSchemaBasedDecoder();
decoder.configure( TLcdISO19115DataTypes.getDataModel() );
return decoder.decode( "document.xml" );

The XML framework also provides the more basic interface ILcdXMLSchemaBasedDecoderLibrary. A library is used when no such data model is available. You can find a more detailed explanation of how these libraries work in the API Reference.

You can control the decoding process of the TLcdXMLSchemaBasedDecoder as follows:

  • Use the ILcdInputStreamFactory to control the creation of the InputStream instances that the decoder uses.

  • Use the XMLInputFactory to control the creation of the XMLStreamReader that is used by the decoder.

  • Use the EntityResolver2 to resolve schema documents. You can find the EntityResolver2 on the TLcdXMLSchemaBasedMapping associated with the decoder.

Creating a data model based on an XML schema

This section explains how to create and customize a data model for an XML schema. Refer to Unified object access for more information on data models.

Mapping XML types on Java classes

The main responsibility of the TLcdXMLDataModelBuilder class is to create a data model of which the data types map on the types defined in an XML schema. Given an XML schema, this class creates a data model according to a number of pre-defined rules. The most important rules are the following:

  • The builder creates a data type in the data model for each type defined in the XML schema. This data type will have the same name as the XML schema type. For anonymous types, a unique name is derived from the location where the type is defined.

  • Complex types map on data object types, simple types on primitive types.

  • Type inheritance in XML schema maps directly on type inheritance. In other words, two types that inherit from each other in XML schema are mapped as two types that inherit from each other in the data model.

  • The following list shows how complex content maps on properties:

    • Attributes: typically one property per attribute

    • Elements: typically one property per element declaration

    • Character content: one property

  • Multi-valued elements (elements which have a maxOccurence larger than 1) map on list properties.

  • All data types inherit the instance class from their parent type. The instance class for the XML schema type anyType is ILcdDataObject, for anySimpleType it is Object.

You can find the complete and detailed list of rules in the API Reference of the TLcdXMLDataModelBuilder class.

To illustrate the use of the decoder, have a look at the schema shown in Program: A simple XML schema. This schema defines two types: the anonymous type of the Model element and the global Address type.

Program: A simple XML schema
<xsd:element name="Model">
 <xsd:complexType>
   <xsd:sequence>
     <xsd:element ref="tns:address" minOccurs="0" maxOccurs="unbounded" />
   </xsd:sequence>
  </xsd:complexType>
</xsd:element>

<xsd:element name="address" type="tns:AddressType" />

<xsd:complexType name="AddressType">
  <xsd:sequence>
    <xsd:element name="street" type="xsd:string" />
    <xsd:element name="number" type="xsd:int" />
    <xsd:element name="city" type="xsd:string" />
  </xsd:sequence>
</xsd:complexType>

TLcdXMLDataModelBuilder maps the anonymous type of the Model element on a type named _Model with a single list property of type Address. Note that the name of this data type starts with an underscore to indicate that it maps on the anonymous type of a global element. The Address type is mapped on a data type named Address. This data type has three properties: street (a String), number (an Integer) and city (a String).

The TLcdXMLDataModelBuilder annotates all data models it creates with TLcdXMLSchemaMappingAnnotation and TLcdXMLSchemaTypeMappingAnnotation annotations. These annotations carry the XML mapping and XML schema information.

Program: Creating a data model for an XML schema shows how to create a data model based on the sample schema.

Program: Creating a data model for an XML schema
private static TLcdDataModel createDataModel() {
  TLcdXMLDataModelBuilder builder = new TLcdXMLDataModelBuilder();
  return builder.createDataModel("http://www.luciad.com/samples.xml", "path/to/schema.xsd");
}

Once the data model has been created, you can configure a decoder with it. This ensures that the decoder uses the appropriate domain classes during decoding. Program: Decoding XML documents shows this for the sample data model.

Program: Decoding XML documents
public Object decode(String document) throws IOException, XMLStreamException {
  TLcdXMLSchemaBasedDecoder decoder = new TLcdXMLSchemaBasedDecoder();
  decoder.configure(createDataModel());
  return decoder.decode(document);
}

Custom domain classes

In many cases, you want to use dedicated domain classes for certain XML schema types. That way, you can implement certain interfaces and add methods to make your domain classes easier to use. If the domain class implements ILcdDataObject and follows the mapping rules of TLcdXMLDataModelBuilder then you only need to set the instance class of the corresponding type.

Program: Using custom domain classes shows how custom domain classes are configured for the xml.customdomainclasses sample. First a TLcdDataModelBuilder is created. This builder is passed to a TLcdXMLDataModelBuilder which creates all types and properties for the given XML schema. Then the data model builder is used to set the instance classes for the Address and _Model types to the domain classes Address and Model. Both classes extend TLcdDataObject.

Program: Using custom domain classes (from samples/xml/customdomainclasses/CustomDomainClassesDataTypes)
private static TLcdDataModel createDataModel() {
  TLcdXMLDataModelBuilder builder = new TLcdXMLDataModelBuilder();
  TLcdDataModelBuilder dataModelBuilder = new TLcdDataModelBuilder("sample");
  builder.buildDataModel(dataModelBuilder, "http://www.luciad.com/samples.xml.customdomainclasses",
                         Main.class.getResource("/samples/xml/customdomainclasses/samples.xml.customdomainclasses.xsd").toString());
  dataModelBuilder.typeBuilder("_Model").instanceClass(Model.class);
  dataModelBuilder.typeBuilder("AddressType").instanceClass(Address.class);
  return dataModelBuilder.createDataModel();
}

Custom XML decoding

You can also fully customize the XML mapping, encoding and decoding process. This allows you, for example, to have domain classes which do not implement ILcdDataObject or to provide custom code for decoding.

Custom simple types

A typical use case is to provide a custom domain class for an XML schema simple type. You can do this by performing the following four steps:

  1. Set the instance class of the type to the custom domain class. This is exactly the same as shown in Program: Using custom domain classes.

  2. Provide an instance of ILcdXMLDatatypeUnmarshaller. This interface defines the contract for unmarshalling XML simple types. Implementations of this interface basically transform text content into a Java object.

  3. Define a custom ILcdXMLSchemaBasedDecoderLibrary. Such a library is responsible for the configuration of TLcdXMLSchemaBasedDecoder instances for the data model. This is done by extending the default TLcdXMLDataObjectDecoderLibrary class with additional code that registers the custom ILcdXMLDatatypeUnmarshaller.

  4. Associate the custom decoder library with the data model by adding a TLcdXMLSchemaMappingAnnotation on the data model.

The xml.binding.custom sample illustrates this. It defines the XML schema type ColorType as a simple type that extends xsd:int. In the Java domain model, this ColorType is mapped on the java.awt.Color class. Program: Custom marshalling and unmarshalling for the ColorType shows the datatype unmarshaller for the Color class. Note that this fragment also shows the implementation of the ILcdXMLDatatypeMarshaller that is required for encoding.

Program: Custom marshalling and unmarshalling for the ColorType (from samples/xml/customdecodingencoding/ColorDatatypeAdapter)
public class ColorDatatypeAdapter implements ILcdXMLDatatypeMarshaller<Color>, ILcdXMLDatatypeUnmarshaller<Color> {

  @Override
  public String marshal(Color aValue, XMLStreamWriter aWriter,
                        ILcdXMLDocumentContext aContext) throws XMLStreamException {
    return Integer.toString(aValue.getRGB());
  }

  @Override
  public Color unmarshal(String aLexicalValue, XMLStreamReader aReader,
                         ILcdXMLDocumentContext aContext) throws XMLStreamException {
    return new Color(Integer.parseInt(aLexicalValue));
  }

}

Program: A custom decoder library defines the CustomDecoderLibrary as an extension of the TLcdXMLDataObjectDecoderLibrary. The class overrides the doConfigure method and registers a new instance of the ColorDatatypeAdapter with the decoder. The adapter is registered for the XML schema type ColorType and the java class Color.

Program: A custom decoder library
class CustomDecoderLibrary extends TLcdXMLDataObjectDecoderLibrary {

  ...

  @Override
  protected void doConfigure( TLcdXMLSchemaBasedDecoder aDecoder ) {
    super.doConfigure( aDecoder );
    aDecoder.getTypeUnmarshallerProvider().registerDatatypeUnmarshaller(
      CustomConstants.COLOR_TYPE_ID,
      Color.class,
      new ColorDatatypeAdapter() );
  }

}

Finally, Program: A custom schema mapping shows how the data model is annotated with a TLcdXMLSchemaMappingAnnotation with a new instance of the CustomDecoderLibrary.

Program: A custom schema mapping (from samples/xml/customdecodingencoding/CustomDecodingEncodingDataTypes)
// associates the custom decoder and encoder libraries with the data model
dataModelBuilder.annotateFromFactory(new DataModelAnnotationFactory<TLcdXMLSchemaMappingAnnotation>() {
  @Override
  public TLcdXMLSchemaMappingAnnotation createAnnotation(TLcdDataModel aDataModel) {
    return new TLcdXMLSchemaMappingAnnotation(
        new MappingLibrary(aDataModel),
        new DecoderLibrary(aDataModel),
        new EncoderLibrary(aDataModel)
    );
  }
});

Note that this snippet uses the CustomMappingLibrary. This library is not strictly necessary for a simple custom type. It is necessary in case of custom complex types as explained in Custom complex types.

Custom complex types

Is it also possible to fully customize the mapping of complex XML schema types. The two main differences with customizing simple types are that the TLcdXMLSchemaBasedMapping should be properly configured and that the decoding process of complex types is typically a hierarchical process that uses delegation to many other unmarshallers.

The main responsibility of the TLcdXMLSchemaBasedMapping is to keep track of information that is common to both encoding and decoding. This includes information about XML schema elements and types. It also manages the ILcdXMLObjectFactory instances that are responsible for creating new instances for a certain XML schema type. Both the TLcdXMLSchemaBasedEncoder and the TLcdXMLSchemaBasedDecoder aggregate a TLcdXMLSchemaBasedMapping instance.

To decode XML complex types, an instance of ILcdXMLTypeUnmarshaller is used. This instance needs to be registered just like an ILcdXMLDatatypeUnmarshaller on the TLcdXMLSchemaBasedDecoder.

The xml.binding.custom sample shows how this can be done in code. The sample defines the XML schema type PointType as a complex type with two attributes (x and y). In the Java domain model, this PointType is mapped on the TLcdLonLatPoint class. This class does not implement ILcdDataObject, so you need to map it as a primitive type. Program: Mapping a complex XML schema type on a primitive type shows how you can do this using a custom extension of TLcdXMLDataModelBuilder. By overriding the buildType method you can control how the data model type is defined for an XML schema type. In this case, the type is defined as a primitive type with instance class ILcd2DEditablePoint. Note that you need an extension because otherwise the type would be defined as a data type with two properties.

Program: Mapping a complex XML schema type on a primitive type
TLcdXMLDataModelBuilder builder = new TLcdXMLDataModelBuilder() {

  @Override
  protected void buildType( TLcdDataTypeBuilder aTypeBuilder, TLcdXMLSchemaTypeIdentifier aTypeId ) {
    if ( aTypeBuilder.getName().equals( "PointType" ) ) {
      aTypeBuilder.instanceClass( ILcd2DEditablePoint.class );
      aTypeBuilder.primitive( true );
    } else {
      super.buildType( aTypeBuilder, aTypeId );
    }
  }

});

Program: A custom object factory shows how the MappingLibrary class defines and configures the object factory for the PointType. Note that this mapping library also registers the ILcd2DEditablePoint interface with the TLcdXMLJavaClassResolver of the mapping. Exporting classes using the TLcdXMLJavaClassResolver explains in more detail why you need to do this.

Program: A custom object factory (from samples/xml/customdecodingencoding/MappingLibrary)
class MappingLibrary extends TLcdXMLDataObjectMappingLibrary {

  public MappingLibrary(TLcdDataModel aDataModel) {
    super(aDataModel);
  }

  @Override
  protected void doConfigureMapping(TLcdXMLSchemaBasedMapping aMapping) {
    super.doConfigureMapping(aMapping);
    aMapping.getTypeObjectFactoryProvider().registerTypeObjectFactory(
        CustomDecodingEncodingConstants.POINT_TYPE_ID,
        ILcd2DEditablePoint.class, new ILcdXMLObjectFactory<ILcd2DEditablePoint>() {

      @Override
      public ILcd2DEditablePoint createObject(ILcdXMLDocumentContext aContext) {
        return new TLcdLonLatPoint();
      }

      @Override
      public ILcd2DEditablePoint resolveObject(ILcd2DEditablePoint aObject, ILcdXMLDocumentContext aContext) {
        return aObject;
      }
    }
                                                                     );
    // register the interface
    List<Class<?>> interfaces = new ArrayList<Class<?>>();
    interfaces.add(ILcd2DEditablePoint.class);
    aMapping.getJavaClassResolver().registerClassPriorityList(interfaces);
  }

}

Program: Custom schema type unmarshalling shows how the ILcdXMLTypeUnmarshaller for the PointType moves the point to the location determined by the x and y attributes. Note that the type marshaller does not need to create a new instance; this is already done by the object factory.

Program: Custom schema type unmarshalling (from samples/xml/customdecodingencoding/PointTypeAdapter)
@Override
public ILcd2DEditablePoint unmarshalType(ILcd2DEditablePoint aObject,
                                         XMLStreamReader aReader, ILcdXMLDocumentContext aContext)
    throws XMLStreamException {
  String x = aReader.getAttributeValue(CustomDecodingEncodingConstants.NAMESPACE_URI, "x");
  String y = aReader.getAttributeValue(CustomDecodingEncodingConstants.NAMESPACE_URI, "y");
  aObject.move2D(Double.parseDouble(x), Double.parseDouble(y));
  aReader.nextTag();
  return aObject;
}

Advanced type unmarshalling

In general, writing an ILcdXMLTypeUnmarshaller can be much more complex than shown in the examples of the previous section. Fortunately, the XML framework is designed to help you as much as possible with this as described in this section.

Unmarshalling contract

The core interface of schema type unmarshalling is ILcdXMLTypeUnmarshaller. The contract of this interface defines how a schema type is unmarshalled into a Java model object. The interface defines a single unmarshalType method. The responsibility of this method is to unmarshal the attributes and content defined in a given XML type.

At the moment this method is called, the XMLStreamReader is positioned at the start tag of the element to be unmarshalled. The type of this element is the given XML type or an extension of that type. When this method returns, the cursor of the XMLStreamReader should be left at the start tag of the first child element that cannot be handled by this unmarshaller. Or, if no such element exists, at the end tag of the element to be unmarshalled. The former typically happens in case the element is of a derived type that has been extended with additional elements.

The object into which the contents of the XML element should be unmarshalled is passed as an argument to the method. Only in case this argument is null, the method should create a new Java object. This typically happens when there is no appropriate ILcdXMLObjectFactory registered or if the factory cannot create a new instance.

Dealing with type extension

The ILcdXMLTypeUnmarshaller interface supports extension by allowing to chain a set of schema type unmarshallers corresponding to the XML Schema type hierarchy. In this chain, each unmarshaller is responsible for unmarshalling only those attributes and child elements which are declared in the corresponding type. In order to set up such a chain, the TLcdXMLSchemaBasedDecoder provides a TLcdXMLTypeUnmarshallerProvider that you can use to find an appropriate schema type unmarshaller for a given schema type and Java class. This provider is also used to find an appropriate parent type unmarshaller.

When a type unmarshaller needs to unmarshal an object, it first unmarshals its own declared attributes, then optionally delegates to its parent and finally unmarshals its own declared elements and content. This approach is consistent with XSD type extension since XML does not define an order on attribute and since elements of extended types always appear after the elements of the super types. Note that in order for this chaining to work, the type unmarshaller should not consume the start element and end element event. These events are handled automatically by the framework.

The ILcdXMLTypeUnmarshaller of a restricted type should not delegate to its parent type.

To be able to reuse common functionality, the xml.bind.custom sample introduces a common ancestor class, AbstractSchemaTypeAdapter. This class structures the (un)marshalling code and provides some utility methods to, for example, (un)marshal child elements, skip whitespace, and more. Program: Type unmarshalling shows how the basic contract of the ILcdXMLTypeUnmarshaller interface is implemented. First, if no instance is passed as argument, a new instance that represents the current XML element in the model, is created. Then the attributes declared in this type are processed. If the type has a base type, then the base type’s unmarshaller is called. Finally, the type unmarshaller unmarshals its declared content and the new instance is returned. The createNewInstance, unmarshalDeclaredAttributes and unmarshalDeclaredContent are placeholder methods that need to be overridden by subclasses.

Program: Type unmarshalling (from samples/xml/customdecodingencoding/AbstractSchemaTypeAdapter)
@Override
public T unmarshalType(T aObject, XMLStreamReader aReader,
                       ILcdXMLDocumentContext aContext) throws XMLStreamException {
  if (aObject == null) {
    aObject = createNewInstance();
  }
  unmarshalDeclaredAttributes(aObject, aReader, aContext);
  ILcdXMLTypeUnmarshaller<? super T> parentMarshaller = getParentUnmarshaller();
  if (parentMarshaller != null) {
    parentMarshaller.unmarshalType(aObject, aReader, aContext);
  } else {
    aReader.next(); // consume start element
  }
  unmarshalDeclaredContent(aObject, aReader, aContext);
  return aObject;
}

private ILcdXMLTypeUnmarshaller<? super T> getParentUnmarshaller() {
  if (fParentID == null) {
    return null;
  }
  return getSchemaDecoder().getTypeUnmarshallerProvider().getTypeUnmarshaller(fParentID, fParentClass, true);
}

Child elements

When a type unmarshaller needs to unmarshal child elements, it looks up an appropriate unmarshaller for its children using the TLcdXMLSchemaBasedUnmarshallerProvider of the TLcdXMLSchemaBasedDecoder.

At this point, you need to know how the child element is defined. In XML schema, child elements can either be locally defined or be references to global elements. An important difference between local and global elements is that XML Schema allows substitution of global elements by other elements. This means that a document can use another element, instead of the declared global element, without becoming invalid. An important constraint here is that XML schema only allows elements that are in the global element’s substitution group as replacement elements. Substitution groups are explicitly defined in the XML schema. Only elements of the same type or of an extension of the type of the substituted element can be part of a substitution group. As such, you can use substitution groups to model polymorph relations.

The important consequence for the type unmarshaller is that only for locally defined elements the XML schema type is known in advance. Because of potential substitution of global elements, you cannot determine the type of the element based on the schema alone. Note that, next to substitution groups, XML defines a second mechanism called xsi:type that enables the use of extension types in a document. This mechanism is currently not supported by the LuciadLightspeed XML framework.

To unmarshal a local element, the type unmarshaller should look up an appropriate ILcdXMLUnmarshaller based on the schema element identifier and the required Java class. Program: Finding an unmarshaller for a local element shows how the xml.customdecodingencoding sample does this. The returned ILcdXMLUnmarshaller is then used to unmarshal the local element into a Java object of the given class.

Program: Finding an unmarshaller for a local element (from samples/xml/customdecodingencoding/AbstractSchemaTypeAdapter)
protected <U> ILcdXMLUnmarshaller<? extends U> getUnmarshaller(TLcdXMLSchemaElementIdentifier aSchemaElement,
                                                               Class<U> aClass) {
  return getSchemaDecoder().getUnmarshallerProvider().getUnmarshaller(aSchemaElement, aClass);
}

To unmarshal a global element, the type unmarshaller should ask the framework for an appropriate unmarshaller based on the current element name and the required Java class. The TLcdXMLUnmarshallerProvider of the TLcdXMLSchemaBasedDecoder consults the registered information about substitution groups to find an appropriate result. The advantage of this approach is that the parent unmarshaller does not have to know anything about the substitutes of the child element. As a consequence, extending the substitution group with a new element can simply be done by registering the unmarshaller for the new element on the unmarshaller provider, without having to modify the parent type’s unmarshaller code. Program: Unmarshalling a global child element shows how this is implemented in the xml.customdecodingencoding sample.

Program: Unmarshalling a global child element (from samples/xml/customdecodingencoding/AbstractSchemaTypeAdapter)
protected <U> U unmarshalChild(XMLStreamReader aReader, Class<U> aClass, ILcdXMLDocumentContext aContext) throws XMLStreamException {
  ILcdXMLUnmarshaller<? extends U> unmarshaller = getSchemaDecoder().getUnmarshallerProvider().
      getUnmarshaller(aReader.getName(), aClass);
  U result = unmarshaller.unmarshal(aReader, aContext);
  return result;
}

An example

The xml.binding.custom sample provides an example of a more complex custom type unmarshaller for the ExtendedAddressType XML schema type. Program: Definition of the ExtendedAddressType shows how this type extends the AddressType with a local and a global element.

Program: Definition of the ExtendedAddressType
<xsd:complexType name="ExtendedAddressType">
  <xsd:complexContent>
    <xsd:extension base="base:AddressType">
      <xsd:sequence>
        <xsd:element ref="tns:location" />
        <xsd:element name="color" type="tns:ColorType" />
      </xsd:sequence>
    </xsd:extension>
  </xsd:complexContent>
</xsd:complexType>

Program: Unmarshalling child elements shows the type unmarshaller for the ExtendedAddressType. It is designed such that it can also correctly decode elements where the location and the color child elements are switched. When the reader’s current name equals color, a color object is decoded. This is done using the color unmarshaller for the local color element. Note that this unmarshaller can be cached in the adapter because the type of the color element is fixed from the XML schema. When the reader’s current name is in the substitution group of the global location element, the location is unmarshalled based on the current name and the ILcd2DEditablePoint class. When an unknown element is encountered, an exception is thrown.

Program: Unmarshalling child elements (from samples/xml/customdecodingencoding/ExtendedAddressTypeAdapter)
protected void unmarshalDeclaredContent(ExtendedAddress aResult, XMLStreamReader aReader,
                                        ILcdXMLDocumentContext aContext) throws XMLStreamException {
  skipAllWhiteSpace(aReader);
  while (aReader.isStartElement()) {
    // expect either location or color
    if (aReader.getName().equals(CustomDecodingEncodingConstants.COLOR_ELEMENT_ID.getElementNames()[0])) {
      aResult.setColor(getColorUnmarshaller().unmarshal(aReader, aContext));
    } else if (getSchemaDecoder().getMapping().getSchemaSet().isSubstitutableBy(CustomDecodingEncodingConstants.LOCATION_ELEMENT_ID.getElementName(), aReader.getName())) {
      aResult.setLocation(unmarshalChild(aReader, ILcd2DEditablePoint.class, aContext));
    } else {
      throw new XMLStreamException("Unexpected element", aReader.getLocation());
    }
    aReader.nextTag();
  }
}

private ILcdXMLUnmarshaller<? extends Color> getColorUnmarshaller() {
  if (fColorUnmarshaller == null) {
    fColorUnmarshaller = getUnmarshaller(CustomDecodingEncodingConstants.COLOR_ELEMENT_ID, Color.class);
  }
  return fColorUnmarshaller;
}

Advanced features

Exporting classes using the TLcdXMLJavaClassResolver

In a typical XML-to-Java binding, there is a one-on-one mapping between XML elements and Java classes. In the XML binding framework, this means that a marshaller is normally registered for a specific XML element and specific Java class. However, there are cases in which this mechanism is not powerful enough:

  • Users of a Java domain model might want to extend a class to add some functionality, but map the extension class on the same XML element as its super class. This would require to register the marshaller for all extension classes as well, which is not always possible (it might be unknown at the moment an XML library is written which extensions will be made in the future).

  • Users might want to bind an XML element to a Java interface, instead of a class. This becomes complex however when a class implements multiple interfaces: to which interface should the framework bind the class to?

The TLcdXMLSchemaBasedMapping has a TLcdXMLJavaClassResolver which provides functionality for mapping a Java class to another class or an interface. Whenever no marshaller is found for the class to be marshalled, this resolver makes a list of all Java classes/interfaces that this class extends or implements, and choose one of them to bind against, based on a priority list. This allows to register marshallers on the marshaller provider for super classes or interfaces only, and let the class resolver take care of the mapping of derived classes to one of these base classes/interfaces.

Runtime schema extension

The XML framework also provides support for runtime schema extension. This allows decoding of XML documents of which the XML schema is not known in advance. A particular interesting case here is the situation where a subset of the schemas used in a document is known in advance, as is the case with, for example, GML. The standardized GML schema defines the core geographic information concepts and is abstract. Applications that want to express content in GML need to define an extension to GML in a specialized application schema. Such an application schema defines the concepts that are specific to that application making use of and possibly extending from existing GML types and elements. The support for runtime schema extensions then makes it possible to decode documents of application schemas using the default GML decoder.

In order to support runtime schema extension, the Java model classes for which the types can be extended by extension schemas, need to implement ILcdDataObject.

To enable schema extension, a decoder needs to be created with a TLcdXMLDataObjectSchemaHandler. This handler reads all unknown schemas encountered. For all these schemas, it creates a new data model and configures the decoder(encoder) with it. This allows the decoder to handle these schemas as well.