Skip to content

Parser (XML to Java)

The parser's main usage is to access the business content of the MX message from a business oriented Java model instead of dealing with the underlying xml structure.

Therefore the parser allows the conversion of an XML message into a Java model that represents the message and provides specific getters to retrieve the elements read from the XML.

It is important to remark that the parser will only accept well structured XML files (all tags properly closed) and it will only read the portions according to the corresponding MX message, meaning any unexpected xml content will be dropped. Also to remark, the parser does not perform any content validation regarding tags repetitions, charsets, qualifiers or semantics.

There are several entry points for the MX parsing feature depending on the use case:

Parsing a specific known message type

When the specific MX message type is known a specific MX message object can be created from String or InputStream, as follows:

MxCamt00300104 mx = MxCamt00300104.parse(xml); 

The above code is also available in constructors.

This is the more efficient way to read the message if the specific version is known in advance. The parser will read into the output object both the Document and the AppHdr (if present).

The implementation is based on the JAXB2 unmarshaller.

Parsing an unknown message type

When the specific category and version of the message to read is unknown you can use the base calss of the message hierarchy to do the parsing, then cast if necessary to extract specific data.

AbstractMX mx = AbstractMX.parse(xml);

if ("camt.048.001.03".equals(mx.getMxId().id())) {
    MxCamt04800103 camt = (MxCamt04800103) mx;
    System.out.println("Message id: " + camt.getModfyRsvatn().getMsgHdr().getMsgId());
}

The MX message model classes, such as MxPacs00800108, that extend the AbstractMX are designed to parse and create specific message types.

The AbstractMX is simply the parent class of the model hierarchy, and it is abstract. Thus, it is not intended to create messages from scratch, and in fact, you cannot create a new empty instance of it. It is only created in parse method calls.

If you parse an XML with a specific model class, any element in the XML that does not belong to the specific schema is automatically dropped. This is because the model classes are designed for specific types. This means that you cannot parse an element such as /Document/GrpHdr/InstrctdAgt into a model if that model does not define such a path or element.

When you parse an XML with the AbstractMX, the API internally autodetects the message type from the namespace and parses the XML with a specific model class. The AbstractMX is just a way to process messages without autodetecting or creating specific instances yourself.

Parsing many unknown message types

When you need to parse many unknown message types and just store the content in a database or read generic metadata that is common to all messages (such as the message type, sender, receiver and reference) you can use the generic MxSwiftMessage class for parsing:

MxSwiftMessage msg = MxSwftMessage.parse(xml);

The class implements several constructors and static methods to create the instance from different sources; String, InputStream, AbstractMX, etc...

Only the header will be parsed in order into structured attributes, while the plain input XML will be stored as a String.

This model object is also suited for persistence because it can be stored in a simple common table for all message types.

If you need to read specific attributes not available in the generic metadata, you can use the specific classes such as MxSese02300201 to read specific data.

Reading specific attributes from many message types

Finally, you have yet another option if you need to read many attributes (not available in the MtSwiftMessage metadata) from many known or unknown message types. This is an alternative, low-level approach where the model objects are not used, and the underlying XML structure is directly traversed.

MxNode n = MxNode.parse(document);
String amount = n.singlePathValue("/Document/etc");

This MxNode is a generic, lightweight, and fast XML tree representation that can be used to traverse or build any XML. You can think of it as a simplified DOM. It lacks several features of a full DOM but is much faster and lighter.

The MxNode model and all its methods are part of a generic XML API. Its purpose is to provide an easy-to-use, generic XML handling API to avoid using Java's native APIs such as DOM, SAX, Stax, etc.

The MxNode model is not specific to ISO 20022. You can use it to parse or create any XML structure.

In particular, you can use the MxNode model to generically parse or create MX messages. However, the model itself does not impose any restrictions on what you can do.

Performance: JaxbContext cache

The parse methods available in the specific classes such as MxCamt00300104 or in the AbstractMX class uses JAXB unmarshaller. By default implementation will create a new JAXBContext for each parse invocation. While this is fine when you need to parse a low number of messages it might become a considerable performance issue for larger volumes.

To deal with it the implementation supports injecting a cache for the JaxbContext.

This is managed by a singleton class JaxbContextLoader like this:

JaxbContextLoader.INSTANCE.setCacheImpl(new JaxbContextCacheImpl());
When a cache implementation is set in the loader, the created JAXBContext will be cached and re-used between parse and write calls. Meaning if you parse a sese.023.002.01, the first time the context will be initialized, but afterwards each time you parse another sese.023.002.01 the available context will be reused.

The JaxbContextCacheImpl is a default out-of-the-box cache implementation based on a simple ConcurrentHashMap, with no eviction. This implementation is aimed to avoid additional third party dependencies. Meaning it can be used right away and it is a fair solution for the performance issue. However, since it has no eviction, if you process a wide variety of message types the cache will grow, consuming a lot of memory.

For a more robust solution You can easily implement your own cache with Guava, Ehcache, Caffeine or any other cache library. For instance if your application already has the Guava library, you can write a Guava based cache like this:

public class GuavaJaxbContextCache implements JaxbContextCache {

    private final Cache<Class<? extends AbstractMX>, JAXBContext> cache = CacheBuilder.newBuilder().maximumSize(100).build();

    public JAXBContext get(final Class<? extends AbstractMX> messageClass, final Class<?>[] classes) throws ExecutionException {
        return cache.get(messageClass, () -> JAXBContext.newInstance(classes));
    } 
}

And then inject this implementation to the loader with:

JaxbContextLoader.INSTANCE.setCacheImpl(new GuavaJaxbContextCache());
The Guava based example code is available at: https://github.com/prowide/prowide-iso20022-examples/