Skip to content

AS2

Purpose

AS2 Messaging Support

This DSL allows the specification of AS2 Trading Partner Configuration and the sending and receiving of AS2 messages and MDNs. The DSL is designed to be used in conjunction with the Platform 6 Routes Service (for inbound requests), the p6.securesocket DSL (for outbound requests) and the Platform 6 Email Service for asynchronous delivery of MDNs

The following encryption algorithms are supported:

  • “NONE”
  • “3DES”
  • “RC2”
  • “IDEA”

The following signature algorithms are supported:

  • “NONE”
  • “SHA1”
  • “SHA256”
  • “MD5”

The following MDN delivery options are supported:

  • “NONE”
  • “SynchronousUsingSHA1”,
  • “SynchronousUsingSHA256”,
  • “SynchronousUsingMD5”,
  • “AsynchronousEmailUsingSHA1”,
  • “AsynchronousEmailUsingSHA256”,
  • “AsynchronousEmailUsingMD5”,
  • “AsynchronousHTTPUsingSHA1”,
  • “AsynchronousHTTPUsingSHA256”,
  • “AsynchronousHTTPUsingMD5”

Note

The string values above are required when defining an AS2 Partner Configuration The default behaviour of the DSL is to use “3DES” encryption with “SHA1” signature, compression enabled and MDN delivery type “SynchronousUsingSHA1”

XML as the DSL interchange Format

To provide seamless integration with Platform 6 the AS2 DSL takes XML documents as input when generating AS2 Messages, creating and returning XML documents representing each received AS2 Message and MDN. This allows straightforward transformation from and update of Platform 6 XML Transactions. Examples are given below.

AS2 Exchange is a complex process with many options. Before proceeding you should be familiar with the specification and message flows. Details can be found here:

Methods

Binding name: p6.as2


Method: AS2PartnerConfiguration buildPartnerConfiguration( AS2PartnerConfiguration partnerConfig, Map<String, String> partnerMap ) throws P6Exception

Build an AS2PartnerConfiguration using the supplied partnerMap. This method takes an existing AS2PartnerConfiguration and appends to it; providing null as partnerConfig wil generate a new AS2PartnerConfiguration.

Note

See the table below for details of the keys to be used in the supplied partnerMap.


Method: AS2PartnerConfiguration loadPartnerConfiguration( String tableId ) throws P6Exception

Build an AS2PartnerConfiguration using the column data of a Platform 6 table. This method takes the id of an existing Platform 6 table as a parameter. It reads the table rows and builds an AS2PartnerConfiguration.

Note

See the table below for details of the table columns to be used in the supplied table.


Method: Tuple4<Integer, String, String, String> postAndWaitResponse( String xmlConf, Map<String, String> parameters, AS2PartnerConfiguration as2PartnerConf, CloseableHttpClient httpClient, HttpPost postMethod ) throws P6Exception

Create an AS2 Message using the supplied xmlConf for sender/recipient and payload details. The generated message will be sent via the supplied HTTP POST using the supplied httpClient. A parameters map allows additional behavioural attributes to be configured (see table later in the document for details)

The method returns a Tuple4 containing the following:

  • Integer: the HTTP status code response to the POST request (200 is good)
  • String: response/error text message
  • String: Response entity (either from the HTTP response directly or converted to XML in the case of a synchronous MDN)
  • String: the Content-Type of the response entity, typically plain/text or application/xml

Method: Tuple5<Boolean, Boolean, String, String, String> sendMessageToStream( String xmlConf, OutputStream os, Map<String, String> parameters, AS2PartnerConfiguration as2PartnerConf ) throws P6Exception

Similar to the method above but excludes the dependency on HTTP transport, instead taking an generic OutputStream as the target of any generated AS2Message.

The method returns a Tuple5 containing the following:

  • Boolean: error indicator (true if error has occurred)
  • Boolean: MDN indicator (true if message written to stream is an MDN)
  • String: error message
  • String: error details
  • String: AS2 Message Id (id of message written to the stream)

Method: Tuple2<String, String> readResponseFromStream( Map<String, String> headers, AS2PartnerConfiguration as2PartnerConf, InputStream is, String messageId ) throws P6Exception

A complement to the sendMessageToStream() method above for processing synchronous MDNs and avoids the dependency on HTTP transport.

This method reads an AS2 Message response input stream and using the supplied AS2PartnerConfiguration validated and parses the MDN.

The original message ID of the message sent is required. The supplied headers are used as MIME headers which are used together with the stream content for AS2 parsing.

The method returns a Tuple2 containing the following:

  • String: Response entity (either from the HTTP response directly or converted to XML in the case of a synchronous MDN)
  • String: the Content-Type of the response entity, typically plain/text or application/xml

Method: Tuple3<Integer, byte[], Map<String, String>> receive( AS2PartnerConfiguration as2PartnerConf, Map<String, String> headers, byte[] ba, Closure<Void> messageReceipt, Closure<Void>...asyncMdnSender )

Receive an AS2 Message (or MDN). This method requires AS2PartnerConfiguration to identify the message sender/recipient. A byte array containing the message content and a separate list of MIME headers are supplied as a Map as parameters.

This method takes one or optionally two callback methods to use during the receipt processing:

  • Closure - messageReceipt: is called when an inbound AS2 Message is identified. The parameters passed are: AS2From Id, MDN Indicator, Message as XML

  • Closure - asyncMdnSender: Only used for Asynchronous MDN send nofification. The parameters passed are: transport name (‘email’ or http’), destination (email address or url), byte array of message body, map of MIME headers

The method returns a Tuple3 containing the following:

  • Integer: the HTTP status code to response with (200 is good)
  • String: the response entity as a byte array (typically a synchronous MDN)
  • Map: map of headers to use in a response to this request

Method: Tuple3<Integer, byte[], Map<String, String>> receive( AS2PartnerConfiguration as2PartnerConf, Map<String, String> headers, java.io.InputStream is, Closure<Void> messageReceipt, Closure<Void>...asyncMdnSender )

Similar to the above but allows the use of a stream rather than a byte array.


AS2 Message Metadata (xmlConf)

Typically created via an XSLT using a Transaction document such as a TransactionInfo.

If the AS2 From/To partner ids are not present in the TransactionInfo XML they can added with a simple token replacement using a String.replaceAll() call or for more complex XML operations consider using the Groovy XMLSlurper:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="2.0">

    <xsl:template match="/TransactionInfo">
        <AS2Message>
            <MessageId><xsl:value-of select="Id"/></MessageId>
            <AS2From>$$AS2FROM$$</AS2From>
            <AS2To>$$AS2TO$$</AS2To>

            <xsl:for-each select="Attachments/Attachment">
                <Part>
                    <URI><xsl:value-of select="URI"/></URI>
                    <ContentType><xsl:value-of select="ContentType"/></ContentType>
                    <Filename><xsl:value-of select="FileName"/></Filename>
               </Part>
            </xsl:for-each>
        </AS2Message>
    </xsl:template>
</xsl:stylesheet>

AS2 Partner Configuration

AS2 Partner Configuration can be loaded via the Platform 6 tables service or via a supplied Map[].

The table column or map key names and their descriptions are listed below:

Column/Key as2.CONSTANT Mandatory Description
id PARTNER_ID Yes - The ID of the partner used to identify partners within platform6.
- Used inside the AS2 Metadata XML above.
as2id PARTNER_AS_2_ID Yes - The AS2 ID of the partner used to identify partners within a AS2 transaction.
- Only used externally; outside of Platform 6
- Can be different according to the environment (test, prod, etc.)
compression PARTNER_COMPRESSION No - Enable message compression.
encryptionAlgorithm PARTNER_ENCRYPTION_ALGORITHM No - Use for encrypting the payload of the AS2 message.
- Choose “NONE” to disable encryption.
mdnOptions PARTNER_MDN_OPTIONS No - How the MDN should be sent.
- Select “NONE” to disable MDN sending.
receiptDeliveryOptions PARTNER_RECEIPT_DELIVERY_OPTIONS No - Async MDN ,only specify when needed.
- Email address or HTTP URL depending of the kind of MDN transmission.
signatureAlgorithm PARTNER_SIGNATURE_ALGORITHM No - Signs messages with MD5 or SHA1.
- Choose “NONE” to deactivate signature for outbound messages.
certificateUrl PARTNER_CERTIFICATE_URL Yes - Required for outbound encrypted messages and/or inbound signed messages.
- DER encoded certificate file path url of the AS2 partner.
privateKeyUrl PARTNER_PRIVATE_KEY_URL No - Required if inbound messages are encrypted and/or outbound messages are signed.
- DER encoded certificate full path of the AS2 partner.

Example AS2 Partner Configurations

Via Table Service

Simply create a table of the required structure and add column values as required:

partner_table1

partner_table2

Via Groovy Map

Loading AS2 Partner Configuration allows the developer to choose any source for their runtime configuration.

def map = [:]

map[p6.as2.PARTNER_ID] = 'partner1'
map[p6.as2.PARTNER_AS_2_ID] = 'as2_partner1'
map[p6.as2.PARTNER_CERTIFICATE_URL] = 'file:${P6_DATA}/resources/certificates/partner1.certificate.der'

AS2PartnerConfiguration conf = p6.as2.buildPartnerConfiguration(null, map)

assert null != conf
println conf

Additional Parameters Passed To The Post/Send Message Methods

Key Description Mandatory
ignoreMissingAttachment Will not fail if attachment are not available No (default to false)
sendEmptyAttachment Will not fail if attachment are available but empty No (default to false)

XML Formatted AS2 Messages returned to Scripts

Decrypted, uncompressed and verified AS2 Messages are converted into an XML representation and made available in your scripts via a callable interface provided to the receive() method(s). The XML can then be used to updated Platform 6 transaction status, attachments and workflow.

<AS2Message>
    <MessageId>Test12345678</MessageId>
    <AS2From>IBM</AS2From>
    <AS2To>PLATFORM6</AS2To>
    <ReceiptDeliveryOption></ReceiptDeliveryOption>
    <DispositionNotificationOptions>signed-receipt-protocol=optional, pkcs7-signature; signed-receipt-micalg=optional, sha1</DispositionNotificationOptions>
    <DispositionNotificationTo>as2@amalto.com</DispositionNotificationTo>
    <ContentMIC>BZDIJy8QFbC0n844x3rg/QsnBus=, sha1</ContentMIC>
    <Part>
        <URI>file:///var/as2/attachments/AS2-part-Invoice_xml-6223574379568382205.dat</URI>
        <ContentType>text/xml; charset=utf-8</ContentType>
        <Filename>Invoice.xml</Filename>
    </Part>
    <Part>
        <URI>file:///var/as2/attachments/AS2-part-Att_pdf-9847574379568335149.dat</URI>
        <ContentType>application/pdf</ContentType>
        <Filename>Att.pdf</Filename>
    </Part>
</AS2Message>
<MDN>
    <AS2From>PLATFORM6</AS2From>
    <AS2To>IBM</AS2To>
    <MessageId>TestBG2_c4ee7c24-1e4d-43f9-a1aa-e8eea0152d10</MessageId>
    <OriginalMessageId>TestBG2</OriginalMessageId>
    <IsSuccessful>false</IsSuccessful>
    <DispositionModifier>failed/Failure: unable to process content</DispositionModifier>
    <OriginalMessageMIC>BZDIJy8QFbC0n844x3rg/QsnBus=, sha1</OriginalMessageMIC>
    <Message>Attempt to process the content of the AS2 Message failed.</Message>
    <IsSigned>true</IsSigned>
    <MDNDigestAlgorithm>SHA1</MDNDigestAlgorithm>
</MDN>

Examples

HTTP POST AS2 Message and Receive Synchronous MDN

The following examples all use a table called: AS2Partners as the source for AS2PartnerConfiguration. They also all use an XSLT called AS2Message to transform a Transaction in the required XML Message Configuration.

import org.apache.http.client.methods.HttpPost
import org.apache.http.client.config.RequestConfig

def TID = 'CTEP_09168_1804_9a_c7369f7a-78c9-4ceb-8298-64f2f85ed518'
def ipk = p6.transaction.buildPK('TransactionInfo', TID)
def transaction = p6.transaction.get( ipk )
assert null != transaction

def xmlAS2Config = p6.xslt.process( "AS2Message", p6.resource.get('AS2Message'), transaction )

def ctx = p6.securesocket.contextBuilder()
    .setTrustSelfSigned( true )
    .build();

def timeout = 60

def config = RequestConfig.custom()
    .setConnectTimeout( timeout * 1000 )
    .setConnectionRequestTimeout( timeout * 1000 )
    .setSocketTimeout( timeout * 1000 )
    .build();

def clientBuilder = p6.securesocket.clientBuilder( ctx )
    .disableAuthCaching()
    .disableAutomaticRetries()
    .disableCookieManagement()
    .setDefaultRequestConfig( config )

def as2PartnerConf = p6.as2.loadPartnerConfiguration('AS2Partners')

def additionalParametersMap = [
    'sendEmptyAttachment': 'false',
    'ignoreMissingAttachment': 'false'
    ]

clientBuilder.build().withCloseable { client ->

    def tpl = p6.as2.postAndWaitResponse( xmlAS2Config, additionalParametersMap, as2PartnerConf, client, new HttpPost( 'https://localhost:8443/p6/public/as2/receive' ) )

    def responseStatus = tpl.first
    def responseReason = tpl.second
    def responseMessage = tpl.third
    def responseMessageContentType = tpl.fourth

    assert responseStatus == 200

    println tpl
}

Receive HTTP POSTed AS2 Message and Generate Synchronous MDN

  1. Route Deployment Script

rest('/public/as2')
    .post('/receive')
    .to('p6cmb://scripts?platform6.request.action=execute&id=AS2SyncInbound')
    .id('AS2AsyncInbound')
2. Route Executed Script (AS2SyncInbound)

def tpl = p6.as2.receive(
    p6.as2.loadPartnerConfiguration('AS2Partners'), 
    p6.pipeline.toStringMap(), 
    p6.pipeline.getBytes('body'), 
    { messageId, isMdn, xml -> println '++++++ Got AS2 Message\nId: ' + messageId + '\nisMDN? ' + isMdn + '\nXML: ' + xml }
)

// --- Build the Http POST response into the pipeline ---

// Integer response code
p6.pipeline.put('CamelHttpResponseCode', '' + tpl.first)
// The body
p6.pipeline.put('body', tpl.second)
// Additional headers - prefixed with 'p6rest.' to ensure they are part of the final HTTP response
tpl.third.each { key, val ->
    p6.pipeline.put( 'p6rest.' + key, val )
}

HTTP POST AS2 Message and Request Asynchronous MDN

\\ No difference to a sync post only targeting a different AS2Partner

Receive HTTP POSTed AS2 Message and Generate Asynchronous MDN Response via Email

  1. Route Deployment Script

rest('/public/as2')
    .post('/areceive')
    .to('p6cmb://scripts?platform6.request.action=execute&id=AS2AsyncInbound')
    .id('AS2AsyncInbound')
2. Route Executed Script (AS2AsyncInbound)

import org.apache.http.client.methods.HttpPost
import org.apache.http.client.config.RequestConfig

def sendAsyncMDN( String deliveryMethod, String destination, byte[] mimeBodyBytes, Map mimeHeadersMap ){
    switch (deliveryMethod) {

        case 'email':
            def additionalParametersMap = [
                'cc': 'simon.temple+mdm@amalto.com',
                'bcc': 'rd@amalto.com'
            ]
            p6.email.sendEmail(destination, mimeBodyBytes, mimeHeadersMap, additionalParametersMap)
            break
    }
}

def as2PartnerConf = p6.as2.loadPartnerConfiguration('AS2Partners')

def tpl = p6.as2.receive(
    as2PartnerConf, 
    p6.pipeline.toStringMap(), 
    p6.pipeline.getBytes('body'), 
    { messageId, isMdn, xml -> println '++++++ RECEIVED AS2 Message\nId: ' + messageId + '\nisMDN? ' + isMdn + '\nXML: ' + xml },
    { deliveryMethod, destination, mimeBodyBytes, mimeHeadersMap -> sendAsyncMDN(deliveryMethod, destination, mimeBodyBytes, mimeHeadersMap) }
)

// --- Build the Http POST response into the pipeline ---

// Response code
p6.pipeline.put('CamelHttpResponseCode', '' + tpl.first)
// The empty or error-message body - MDN is going async
p6.pipeline.put('body', tpl.second)
// Additional headers - prefixed with 'p6rest.' to ensure they are part of the final HTTP response
tpl.third.each { key, val ->
    p6.pipeline.put( 'p6rest.' + key, val )
}

Receive HTTP POSTed AS2 Message and Generate Asynchronous MDN response via HTTP POST

  1. Route Deployment Script
rest('/public/as2')
    .post('/areceive')
    .to('p6cmb://scripts?platform6.request.action=execute&id=AS2AsyncInbound')
    .id('AS2AsyncInbound')

To restrict the content type to only those defined in RFC4130 add a consumes() declaration to the route:

// MUST support Content-Types from rfc4130
rest('/public/as2')
    .post('/receive')
    .consumes('multipart/signed,multipart/report,message/disposition-notification,application/PKCS7-signature,application/pkcs7-mime,application/EDI-X12,application/EDIFACT,application/edi-consent,application/XML')
    .to('p6cmb://scripts?platform6.request.action=execute&id=AS2SyncInbound')
    .id('AS2SyncInbound')
  1. Route Executed Script (AS2AsyncInbound)
import org.apache.http.client.methods.HttpPost
import org.apache.http.client.config.RequestConfig
import org.apache.http.entity.ByteArrayEntity

def ctx = p6.securesocket.contextBuilder()
    .setTrustSelfSigned( true )
    .build();

def timeout = 60

def config = RequestConfig.custom()
    .setConnectTimeout( timeout * 1000 )
    .setConnectionRequestTimeout( timeout * 1000 )
    .setSocketTimeout( timeout * 1000 )
    .build();

clientBuilder = p6.securesocket.clientBuilder( ctx )
    .disableAuthCaching()
    .disableAutomaticRetries()
    .disableCookieManagement()
    .setDefaultRequestConfig( config )


def sendAsyncMDN( String deliveryMethod, String destination, byte[] mimeBodyBytes, Map mimeHeadersMap ){
    switch (deliveryMethod) {
        case 'http':
            clientBuilder.build().withCloseable { httpClient ->
                def postMethod = new HttpPost( destination )
                postMethod.setEntity(new ByteArrayEntity(mimeBodyBytes))
                mimeHeadersMap.each{ k, v -> postMethod.setHeader(k,v) }
                httpClient.execute(postMethod).withCloseable { httpResponse ->
                    println '++++++  POST async MDN response, Status: ' + httpResponse.getStatusLine().getStatusCode() +' Reason: ' + httpResponse.getStatusLine().getReasonPhrase()
                }
            }
            break
    }
}

def as2PartnerConf = p6.as2.loadPartnerConfiguration('AS2Partners')

def tpl = p6.as2.receive(
    as2PartnerConf, 
    p6.pipeline.toStringMap(), 
    p6.pipeline.getBytes('body'), 
    { messageId, isMdn, xml -> println '++++++ RECEIVED AS2 Message\nId: ' + messageId + '\nisMDN? ' + isMdn + '\nXML: ' + xml },
    { deliveryMethod, destination, mimeBodyBytes, mimeHeadersMap -> sendAsyncMDN(deliveryMethod, destination, mimeBodyBytes, mimeHeadersMap) }
)

// --- Build the Http POST response into the pipeline ---

// Response code
p6.pipeline.put('CamelHttpResponseCode', '' + tpl.first)
// The empty or error-message body - MDN is going async
p6.pipeline.put('body', tpl.second)
// Additional headers - prefixed with 'p6rest.' to ensure they are part of the final HTTP response
tpl.third.each { key, val ->
    p6.pipeline.put( 'p6rest.' + key, val )
}

Receive HTTP POSTed Asynchronous MDN response via HTTP POST

  1. Route Deployment Script

rest('/public/as2')
    .post('/mdn')
    .to('p6cmb://scripts?platform6.request.action=execute&id=AS2MDNInbound')
    .id('AS2MDNInbound')
2. Route Executed Script (AS2MDNInbound)

def as2PartnerConf = p6.as2.loadPartnerConfiguration('AS2Partners')

def tpl = p6.as2.receive(
    as2PartnerConf, 
    p6.pipeline.toStringMap(), 
    p6.pipeline.getBytes('body'), 
    { messageId, isMdn, xml -> println '++++++ RECEIVED AS2 MDN\nId: ' + messageId + '\nisMDN? ' + isMdn + '\nXML: ' + xml },
)

// --- Build the Http POST response into the pipeline ---

// Response code
p6.pipeline.put('CamelHttpResponseCode', '' + tpl.first)
// The empty or error-message body
p6.pipeline.put('body', tpl.second)
// Additional headers - prefixed with 'p6rest.' to ensure they are part of the final HTTP response
tpl.third.each { key, val ->
    p6.pipeline.put( 'p6rest.' + key, val )
}