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, typicallyplain/text
orapplication/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, typicallyplain/text
orapplication/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 asXML
-
Closure - asyncMdnSender:
Only used for Asynchronous MDN send nofification
. The parameters passed are: transportname
(‘email’ or http’),destination
(email address or url), byte array of messagebody
, map of MIMEheaders
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:
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¶
- Route Deployment Script
rest('/public/as2')
.post('/receive')
.to('p6cmb://scripts?platform6.request.action=execute&id=AS2SyncInbound')
.id('AS2AsyncInbound')
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¶
- Route Deployment Script
rest('/public/as2')
.post('/areceive')
.to('p6cmb://scripts?platform6.request.action=execute&id=AS2AsyncInbound')
.id('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¶
- 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')
- 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¶
- Route Deployment Script
rest('/public/as2')
.post('/mdn')
.to('p6cmb://scripts?platform6.request.action=execute&id=AS2MDNInbound')
.id('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 )
}