How to Write Your Own Axis2 Transport

Prologue

To stop you from re-inventing the wheel, before we get started, I will quickly list the transports that are already supported in Axis2 with a small description.

  • HTTP - In the HTTP transport, the transport Listener is either a Servlet or a Simple HTTP server provided by Axis2. The transport Sender uses sockets to connect and send the SOAP message. Currently we have the Apache HttpComponents (HttpClient 5) based HTTP Transport sender as the default transport.
  • HTTP/2 - An outbound HTTP/2 transport sender (transport-h2) based on HttpClient 5, with ALPN negotiation and stream multiplexing.
  • TCP - This is the most simple transport, but needs Addressing support to be functional.
  • UDP - A datagram transport for small messages.
  • JMS - Sends and receives messages through a Jakarta Messaging (JMS) provider.
  • Mail - Sends messages with SMTP and polls POP3/IMAP mailboxes for incoming messages.
  • Local - An in-JVM transport, mainly useful for testing.

To understand the rest of this document you will need some understanding of the architecture of Axis2. If you are not familiar with the Axis2 architecture, please go through the Axis2 Architecture Guide before you read any further.

Introduction

Broadly speaking, a transport inside Axis2 can be classified as a way of getting messages that arrive though some channel into the Axis2 engine. The core of Axis2 is transport independent. All data that is transport specific is stripped out of the incoming message and inserted into the MessageContext. In the outgoing message, all transport specific information, like headers, are added and sent.

To write your own transport, you will primarily need to write two classes: one is the TransportSender and the other is the TransportReceiver. To register a transport with Axis2 you will need to put entries corresponding to these two classes in the axis2.xml file. I will take you through the process of adding the entries in the relevant sections.

Transport Receiver

Any message that is coming into Axis2 needs to go through a transport receiver. All information about how the message is received at the Axis2 server from the wire (or via an e-mail) is isolated inside the transport receiver. It extracts the data that is coming on the wire and transforms it into a state that the Axis2 server understands.

So now that we have some background information about how transports work inside Axis2, without further delay, lets dive into some coding and start building our own transport.

To get things stared, you will first need to extend from the org.apache.axis2.kernel.TransportListener interface and write your own transport listener. To create an engine to process the MessageContext, we need a configuration context. The following code fragment will do this. This should ideally be only done once for the lifetime of the Transport receiver.

try {
        //Use an Axis2 repository to create a new Configuration Context
        configurationContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem(repository_directory, 
axis2xmllocation);
} catch (Exception e) {
        log.info(e.getMessage());
}

Now we need some kind of a Listener to listen to the requests that come in. You need to implement this according to the transport that you are trying to build. After a message is received at the Receiver, you can use the following code to process the request and then forward the message context to the engine using the static AxisEngine.receive(msgContext) method. (The following code is a simplified example for a mail-like transport; "message" stands for the transport's own representation of the received message, and "TRANSPORT_NAME" for the name your transport is registered under in axis2.xml.)

MessageContext msgContext = null;

// create and initialize a message context
try {
        TransportInDescription transportIn =
                configurationContext.getAxisConfiguration().getTransportIn(TRANSPORT_NAME);
        TransportOutDescription transportOut =
                configurationContext.getAxisConfiguration().getTransportOut(TRANSPORT_NAME);
        if (transportIn != null && transportOut != null) {
                //create Message Context
                msgContext = configurationContext.createMessageContext();
                msgContext.setTransportIn(transportIn);
                msgContext.setTransportOut(transportOut);
                msgContext.setServerSide(true);
                msgContext.setProperty(Constants.Configuration.CONTENT_TYPE, message.getContentType());
                msgContext.setProperty(Constants.Configuration.CHARACTER_SET_ENCODING, message.getEncoding());

                String soapAction = message.getSOAPActionHeader();
                msgContext.setWSAAction(soapAction);
                msgContext.setSoapAction(soapAction);

                // Here we are trying to set the reply to if it is present in the transport information.
                msgContext.setReplyTo(new EndpointReference(message.getReplyTo());

                //Create the SOAP Message. TransportUtils selects the message builder
                //configured in axis2.xml for the content type (SOAP 1.1, SOAP 1.2, MTOM,
                //JSON, ...), so the transport does not need to parse the payload itself.
                InputStream in = message.getInputStream();
                SOAPEnvelope envelope = TransportUtils.createSOAPMessage(msgContext, in,
                        message.getContentType());
                msgContext.setEnvelope(envelope);
                AxisEngine.receive(msgContext);
        } else {
                throw new AxisFault("Unknown transport " + TRANSPORT_NAME);
        }

} catch (Exception e) {
        try {
                if (msgContext != null) {
                        MessageContext faultContext =
                                MessageContextBuilder.createFaultMessageContext(msgContext, e);
                        AxisEngine.sendFault(faultContext);
                } else {
                        log.error(e);
                }
        } catch (AxisFault e1) {
                log.error(e);
        }
}

Now that we have the coding in place, we need to let Axis2 know about our new transport receiver. We do this by adding an entry into the axis2.xml file. If you need to pass any properties for the transport to operate, it can also be done through the axis2.xml file.

   <transportReceiver name="TRANSPORT_NAME" class="org.example.transport.TRANSPORT_LISTENER_CLASS">
        <parameter name="PROPERTY_NAME">PROPERTY_VALUE</parameter>
        <parameter name="PROPERTY_NAME_2">PROPERTY_VALUE_2</parameter>
  </transportReceiver>
  

By using a code fragment like transportIn.getParameter("PROPERTY_NAME").getValue() we can extract the parameters that we inserted into the axis2.xml file.

As you can see, getting a new transport receiver up and running is a task that requires very little effort.

Transport Sender

Any message that is to be sent out of Axis2, is sent through the Transport Sender. A Transport Sender implements the org.apache.axis2.kernel.TransportSender interface; the easiest way to write one is to extend the org.apache.axis2.transport.base.AbstractTransportSender class (in the axis2-transport-base module), which is what the JMS, mail and UDP transports do.

AbstractTransportSender works out where the message is going and then calls the one abstract method you implement:

public abstract void sendMessage(MessageContext msgCtx, String targetEPR,
        OutTransportInfo outTransportInfo) throws AxisFault;

When the message has an explicit destination (a WS-Addressing To address, or the TRANSPORT_URL property), targetEPR holds that address and your transport opens a new connection to it. When a server sends a response back over the channel the request arrived on, targetEPR is null and outTransportInfo carries the transport-specific information your listener stored on the incoming message context.

After implementing the necessary methods, you can let Axis2 know about your new transport sender by adding an entry to the axis2.xml file, like you did for the TransportReceiver.

  <transportSender name="TRANSPORT_NAME" class="org.example.transport.TRANSPORT_SENDER_CLASS">
        <parameter name="PROPERTY_NAME">PROPERTY_VALUE</parameter>
        <parameter name="PROPERTY_NAME_2">PROPERTY_VALUE_2</parameter>
  </transportSender>
  

Have a look at org.apache.axis2.transport.http.impl.httpclient5.HTTPClient5TransportSender, which is used to send HTTP messages, and at org.apache.axis2.transport.mail.MailTransportSender for a sender built on AbstractTransportSender.

Once we have written our transport receiver and our transport sender, and inserted the required entries into the axis2.xml file, we are done. It is as simple as that!