PIDX RNIF
Purpose¶
Build and send (or receive) RNIF envelopes that contain one of the following PIDX payloads:
- Invoice
- InvoiceResponse
- FieldTicket
- FieldTicketResponse
- Quote
- QuoteNotification
- QuoteRequest
- OrderChange
- OrderCreate
- OrderResponse
- Receipt Acknowledgment
- Quote
- Exception
See:
- http://www.pidx.org/rosettanet-standards/
- http://www.pidx.org/wp-content/uploads/2014/07/PIDX-XML-Implementation-Guideline-_RosettaNet_-version-2_0-2.pdf
Methods¶
Binding name: p6.pidxrnif
Method: Tuple4 postAndWaitResponse( final String xmlConf, final Map< String, String > parameters, final CloseableHttpClient httpClient, final HttpPost postMethod )
Using the supplied XML configuration metadata (see below) and a Map of optional parameters, an RNIF payload is constructed and POSTed to a trading partner using the supplied httpClient and postMethod.
The httpClient and postMethod are from the Apache Http Components API. The httpClient is best built using the securesocket DSL.
The POST response is received and parsed. The Tuple4 object returned contains the following:
- Http Status Code
- Http Response Message
- Http Response Entity
- The Content-Type of the Response Entity
Method: void sendMessageToStream( final String xmlConf, final OutputStream os, final Map< String, String > parameters )
A component method of the postAndWaitResponse()
. Using the supplied XML configuration metadata (see below) and a Map of optional parameters, an RNIF payload is constructed and written to the supplied OutputStream.
Method: Tuple4 readResponseFromStream( int responseCode, String responseMessage, Map< String, String > headers, InputStream is )
A component method of the postAndWaitResponse()
. Given the components of an Http Response, build the standard Tuple4 response structure.
Method: Tuple2< String, String > receive( Map< String, String > headers, InputStream is, final Closure partClosure )
Method: Tuple2< String, String > receive( Map< String, String > headers, byte[] ba, final Closure partClosure )
Trading Partners send RNIF messages to a URL configured using the routes service and p6rest component via an HTTPS POST request. Neither HTTP nor GET requests are advisable. The POST headers and POST entity should be supplied as Map and byte[]/InputStream arguments respectively. HTTPS is not supported directly via the Platform 6 instance. An inbound HTTP(S) proxy is required for this such as NGINX or Apached.
Files (the content + the attachments) will be detached to the p6 temporary folder (e.g /opt/p6core.data/tmp). Each time an RNIF Message is processed the given closure will be called with the following arguments:
- Message Concept
- Message Content as XML
The Tuple2 object returned contains:
- Content-Type of Response Message
- Response message
RNIF Message Metadata¶
Typically created via an XSLT using a Transaction document such as a TransactionInfo:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" exclude-result-prefixes="#all">
<xsl:output method="xml" omit-xml-declaration="no"/>
<xsl:param name="TIMESTAMP"/>
<xsl:param name="CONTEXTID"/>
<xsl:variable name="uri"><xsl:value-of select="/TransactionInfo/CurrentDocumentURI"/></xsl:variable>
<xsl:variable name="SenderDUNS"><xsl:value-of select="/TransactionInfo/KeyValue[Key='SenderDUNS']/Value"/></xsl:variable>
<xsl:variable name="SenderLocationID"><xsl:value-of select="/TransactionInfo/KeyValue[Key='SenderLocationID']/Value"/></xsl:variable>
<xsl:variable name="ReceiverDUNS"><xsl:value-of select="/TransactionInfo/KeyValue[Key='ReceiverDUNS']/Value"/></xsl:variable>
<xsl:variable name="ReceiverLocationID"><xsl:value-of select="/TransactionInfo/KeyValue[Key='ReceiverLocationID']/Value"/></xsl:variable>
<xsl:variable name="MessageID"><xsl:value-of select="/TransactionInfo/Id"/></xsl:variable>
<xsl:variable name="BusinessDocName"><xsl:value-of select="/TransactionInfo/BusinessDocName"/></xsl:variable>
<xsl:template match="/">
<RnifMessage>
<MetaData>
<ReceiverDUNS><xsl:value-of select="$ReceiverDUNS"/></ReceiverDUNS>
<ReceiverLocationID><xsl:value-of select="$ReceiverLocationID"/></ReceiverLocationID>
<SenderDUNS><xsl:value-of select="$SenderDUNS"/></SenderDUNS>
<SenderLocationID><xsl:value-of select="$SenderLocationID"/></SenderLocationID>
<MessageTrackingID><xsl:value-of select="$MessageID"/></MessageTrackingID>
<PipInstanceIdentifier><xsl:choose>
<xsl:when test="$BusinessDocName = 'Quote'"><xsl:value-of select="concat('JDE_QPR_.',$MessageID)"/></xsl:when>
<xsl:otherwise><xsl:value-of select="$MessageID"/></xsl:otherwise>
</xsl:choose></PipInstanceIdentifier>
<Concept><xsl:choose>
<xsl:when test="$BusinessDocName = 'Invoice'">Invoice</xsl:when>
<xsl:when test="$BusinessDocName = 'InvoiceResponse'">InvoiceResponse</xsl:when>
<xsl:when test="$BusinessDocName = 'Field Ticket'">FieldTicket</xsl:when>
<xsl:when test="$BusinessDocName = 'FT Response'">FieldTicketResponse</xsl:when>
<xsl:otherwise><xsl:value-of select="$BusinessDocName"/></xsl:otherwise>
</xsl:choose></Concept>
<GlobalUsageCode>Test</GlobalUsageCode>
</MetaData>
<Content>
<ContentID>pidxContent</ContentID>
<Description>xml</Description>
<MimeType>text/xml</MimeType>
<URI><xsl:value-of select="$uri"/></URI>
</Content>
<Attachments>
<!-- <IgnoreMissingAttachment>false</IgnoreMissingAttachment> -->
<xsl:apply-templates select="//Attachment"/>
</Attachments>
</RnifMessage>
</xsl:template>
<xsl:template match="Attachment">
<Content>
<ContentID>Attachment<xsl:value-of select="position()"/></ContentID>
<Description>Other</Description>
<MimeType><xsl:value-of select="ContentType"/></MimeType>
<URI><xsl:value-of select="URI"/></URI>
</Content>
</xsl:template>
</xsl:stylesheet>
Some additional parameters can be passed to the DSL via a Map which will provide default values when they are not available in the Metadata XML:
XPath | Description |
---|---|
/RnifMessage/MetaData/ReceiverDUNS | The receiver DUNS number |
/RnifMessage/MetaData/ReceiverLocationID | The receiver Location Id parameter |
/RnifMessage/MetaData/SenderDUNS | The sender DUNS number |
/RnifMessage/MetaData/SenderLocationID | The sender Location Id parameter |
/RnifMessage/MetaData/MessageTrackingID | The RNIF unique Message Tracking ID |
/RnifMessage/MetaData/PipInstanceIdentifier | The RNIF PIP Instance Identifier |
/RnifMessage/MetaData/Concept | The document Type Invoice, ReceiptAcknowledgment, Quote, etc… |
/RnifMessage/MetaData/GlobalUsageCode | The RNIF Global usage code; usually Test or Production |
/RnifMessage/Content/URI | The URI to the XML file with the content. The file MUST be UTF-8 encoded. |
/RnifMessage/Content/TransferEncoding | The Content-Transfer-Encoding to set on the Mime Part holding the content, defaults to binary when not specified |
/RnifMessage/Attachments/IgnoreMissingAttachment | If set to true and an attachment file cannot be found at the URI specified in the Metadata XML, the attachment will be ignored and not sent |
/RnifMessage/Attachments/SendEmptyAttachment | If set to true and an attachment file is found at the URI specified in the Metadata XML but is empty, the attachment is sent. Otherwise an error is raised. |
/RnifMessage/Attachments/Content | Optional and can be repeated |
/RnifMessage/Attachments/Content/ContentID | The content ID header |
/RnifMessage/Attachments/Content/Description | The description of the attachment, optional |
/RnifMessage/Attachments/Content/MimeType | The mime type (with charset when appropriate) of the part |
/RnifMessage/Attachments/Content/URI | The URI of the attachment |
/RnifMessage/Attachments/Content/TransferEncoding | The Content-Transfer-Encoding to set on the Mime Part holding the content. Defaults to binary when not specified |
Example Rnif Metadata XML
<?xml version="1.0" encoding="UTF-8"?>
<RnifMessage>
<MetaData>
<ReceiverDUNS>146880422</ReceiverDUNS>
<ReceiverLocationID>Houston</ReceiverLocationID>
<SenderDUNS>065485625</SenderDUNS>
<SenderLocationID>Houston</SenderLocationID>
<MessageTrackingID>065485625|P21|TP_CVX_PIDX_352438_INVOIC_94973051_200712191107049|Invoice|Invoice Notification</MessageTrackingID>
<PipInstanceIdentifier>TP_CVX_PIDX_352438_INVOIC_94973051_200712191107049</PipInstanceIdentifier>
<Concept>Invoice</Concept>
<GlobalUsageCode>Test</GlobalUsageCode>
</MetaData>
<Content>
<URI>file://outbound/Invoice-ContentsXML.xml</URI>
</Content>
<Attachments>
<IgnoreMissingAttachment>false</IgnoreMissingAttachment>
<Content>
<ContentID>Attachment1</ContentID>
<Description>Field Ticket</Description>
<MimeType>application/pdf</MimeType>
<URI>file://outbound/sample.pdf</URI>
</Content>
<Content>
<ContentID>Attachment2</ContentID>
<Description>scan</Description>
<MimeType>application/pdf</MimeType>
<URI>file://outbound/sample2.pdf</URI>
<TransferEncoding>base64</TransferEncoding>
</Content>
</Attachments>
</RnifMessage>
Examples¶
import org.apache.http.client.methods.HttpPost
import org.apache.http.client.config.RequestConfig
// Sixty second timeout
def timeout = 60
// Use Xslt to build configuration meta data from input transaction
def xmlRnifConfig = p6.xslt.process( "RnifMessage", p6.resource.get('RnifMessage'), p6.transaction )
// A few parameter overrides for this script
def parameters = [
'globalUsageCode': 'Test',
'ignoreMissingAttachment': 'false',
'deleteAttachmentLocations': 'true'
]
def config = RequestConfig.custom()
.setConnectTimeout( timeout * 1000 )
.setConnectionRequestTimeout( timeout * 1000 )
.setSocketTimeout( timeout * 1000 )
.build()
// TWO_WAY TLS is a common requirement of RNIF POSTs
def ctx = p6.securesocket.contextBuilder()
.setType( SecureContext.BundleType.TWO_WAY_TRUST_ANY )
.setIdentityPrivateKeyPath( "file://${P6_DATA}/resources/certificates/privatekey.pem" )
.setIdentityCertsPaths( "file://${P6_DATA}/resources/certificates/publickey.pem" )
.build()
// Further customise our httpClient builder
def clientBuilder = p6.securesocket.clientBuilder( ctx )
.disableAuthCaching()
.disableAutomaticRetries()
.disableCookieManagement()
.setDefaultRequestConfig( config )
// Auto-close 'with' block
clientBuilder.build().withCloseable { client ->
def tpl = p6.pidxrnif.postAndWaitResponse( xmlRnifConfig, parameters, client, new HttpPost( "https://httpbin.org/post" ) )
// Extract the response from teh Tuple4
def responseStatus = tpl.first
def responseReason = tpl.second
def responseMessage = tpl.third
def responseMessageContentType = tpl.fourth
assert responseStatus == 200
}
Inbound POST Route
rest('/public/rnif')
.post('/receive')
.consumes('multipart/related;boundary="RN-Http-Body-Boundary";type="multipart/related"')
.produces('text/plain')
.to('p6cmb://scripts?platform6.request.action=execute&id=RNIFInbound')
.id('RNIFInbound')
Once the route is deployed, a POST test can be made to: https://localhost:8443/p6/public/rnif/receive or alternatively to an NGINX mapped destination (recommended)
Script: RNIFInbound
def tpl = p6.pidxrnif.receive( p6.pipeline.toStringMap(), p6.pipeline.getBytes('body'), { concept, xml ->
println '++++++ Got RNIF Part! Concept: ' + concept + '\nXml: ' + xml
})
// Build the Http POST response into the pipeline
p6.pipeline.put("Content-Type", tpl.first)
p6.pipeline.put("body", tpl.second, tpl.first)
p6.pipeline.put("CamelHttpResponseCode", (tpl.second=='OK') ? '200' : '500')