During the migration of interfaces from PI/PO to the Integration Suite, there were XI Protocol-based interfaces that sent attachments. The attachments were supposed to be saved as files on an SFTP server in the target system under their original names. The migration created an iFlow with an XI sender. Unfortunately, the original names of the attachments were lost. A solution has been implemented to save these names and use them when storing files on the SFTP server.
An ABAP proxy can send not only the main payload but also attachments to the XI sender adapter. The attachments in the XI message are appended to the message by the sender adapter.
The following simple example scenario has been developed to illustrate this point. A message containing attachments is sent to Cloud Integration via an ABAP proxy. This message is received by a generic XI sender iFlow, which forwards it to an interface-specific iFlow via ProcessDirect, without losing the attachments or their original names. The specific iFlow should then store the main payload, along with all the attachments, under their original names on an SFTP server.
ABAP Proxy Message Monitor
The sample request shows that the attachments are Readme.txt and TestDocument.pdf. The received iFlow uses a Groovy script to log all the message's attachments, along with their names, in the MPL.
XI Sender iFlow - log attachments
def Message logAttachmentList(Message message) {
// Log attachment infos with original names
def attachments = message.getAttachments()
if (attachments == null || attachments.isEmpty()) return message // No attachments
def line = "----------------------------------------------------------\n"
def attLog = ""
def messageLog = messageLogFactory.getMessageLog(message)
if (messageLog != null){
attLog += "AttachmentCount: " + attachments?.size()?.toString() + "\n" + line
attachments.eachWithIndex { att, index ->
attLog += "Attachment: " + att?.key + "\n"
def attachment = att?.value
attLog += "ContentType: " + attachment?.getContentType() + "\n"
attLog += "Name: " + attachment?.getName() + "\n" + line
}
messageLog.addAttachmentAsString("Attachmentlist.txt", attLog, "text/plain; charset=UTF-8")
}
return message
}Unfortunately, the original names of the attachments are lost in the process, meaning they can no longer be read directly from the attachment object using the getName() method.
AttachmentCount: 2
----------------------------------------------------------
Attachment: [email protected]
ContentType: text/plain
Name: [email protected]
----------------------------------------------------------
Attachment: [email protected]
ContentType: application/pdf
Name: [email protected]
----------------------------------------------------------The message trace shows that the XI sender adapter stores the attachment reference as its name and the manifest content of the XI message in the SAP_XiManifest property. Both the reference and the original name are included here.
Property SAP_XiManifest
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Manifest wsu:Id="wsuid-manifest-5CABE13F5C59AB7FE10000000A1551F7" xmlns="http://sap.com/xi/XI/Message/30" xmlns:wsu="http://www.docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:ns2="http://www.w3.org/1999/xlink">
<Payload ns2:href="cid:[email protected]">
<Name>Readme.txt</Name>
<Description/>
<Type>ApplicationAttachment</Type>
</Payload>
<Payload ns2:href="cid:[email protected]">
<Name>TestDocument.pdf</Name>
<Description/>
<Type>ApplicationAttachment</Type>
</Payload>
<Payload ns2:href="cid:[email protected]">
<Name>MainDocument</Name>
<Description/>
<Type>Application</Type>
</Payload>
</Manifest>
To solve this problem, a second Groovy script is added to preserve the original names. This script analyses the manifest and stores the references as key-value pairs in a map, using the reference as the key and the original name as the value. In order for the map to be passed as a header with the name XI_AttachmentList to the next iFlow, it is converted into JSON format.
XI Sender iFlow
def Message processData(Message message) {
// Get SAP_XiManifest property
def manifestXml = message.getProperty('SAP_XiManifest')
if (manifestXml == null || manifestXml.trim().isEmpty()) {
// No manifest found, return message as-is
return message
}
// Parse the XML and create HashMap to store attachment names with their content IDs as keys
def manifest = new XmlSlurper().parseText(manifestXml)
def attachmentMap = new HashMap<String, String>()
// Find all Payload elements where Type == "ApplicationAttachment",
// Get the name and the href attribute and remove the "cid:" prefix
manifest.Payload.each { payload ->
if (payload.Type.text() == 'ApplicationAttachment') {
def name = payload.Name.text()
def attributes = payload.attributes()
def hrefAttr = null
attributes.each { key, value ->
if (key.toString().contains('href')) {
hrefAttr = value
}
}
if (hrefAttr && hrefAttr.startsWith('cid:')) {
def contentId = hrefAttr.substring(4) // Remove "cid:" prefix
attachmentMap[contentId] = name
}
}
}
// Convert HashMap to JSON string and store in a header
message.setHeader('XI_AttachmentList', JsonOutput.toJson(attachmentMap))
return message
}
Header XI_AttachmentList
Header XI_AttachmentList
{
"[email protected]": "TestDocument.pdf",
"[email protected]": "Readme.txt"
}The second iFlow must allow the header XI_AttachmentList in the runtime configuration. As in the previous iFlow, all attachments are logged in the MPL along with their names using a Groovy script. However, the extended script takes into account the header variable XI_AttachmentList and maps the attachment name to its original name.
def Message logAttachmentListWithNames(Message message) {
// Log attachment infos with original names
def jsonAttList = message.getHeader('XI_AttachmentList', String)
def attachmentNames = [:]
if (jsonAttList != null) attachmentNames = new JsonSlurper()?.parseText(jsonAttList)
def attachments = message.getAttachments()
if (attachments == null || attachments.isEmpty()) attachments = message.getProperty('AttachmentsMap')
if (attachments == null || attachments.isEmpty()) return message // No attachments
def line = "----------------------------------------------------------\n"
def attLog = ""
def messageLog = messageLogFactory.getMessageLog(message)
if (messageLog != null){
attLog += "AttachmentCount: " + attachments?.size()?.toString() + "\n"
attLog += "AttachmentNames:\n"
attachmentNames.eachWithIndex { attNam, index ->
attLog += attNam?.key + " = " + attNam?.value + "\n"
}
attLog += line
attachments.eachWithIndex { att, index ->
attLog += "Attachment: " + att?.key + "\n"
def attachment = att?.value
attLog += "ContentType: " + attachment?.getContentType() + "\n"
attLog += "Name: " + attachment?.getName() + "\n"
if (jsonAttList != null) attLog += "TranslatedName: " + (attachmentNames[att?.key]?:attachment?.getName()) + "\n" + line
}
messageLog.addAttachmentAsString("Attachmentlist.txt", attLog, "text/plain; charset=UTF-8")
}
return message
}
AttachmentCount: 2
AttachmentNames:
[email protected] = TestDocument.pdf
[email protected] = Readme.txt
----------------------------------------------------------
Attachment: [email protected]
ContentType: text/plain
Name: [email protected]
TranslatedName: Readme.txt
----------------------------------------------------------
Attachment: [email protected]
ContentType: application/pdf
Name: [email protected]
TranslatedName: TestDocument.pdf
----------------------------------------------------------To simplify processing all the attachments, a Groovy script reads them into a property as a map. In addition, the JSON content from the XI_AttachmentList header is converted into a map and stored in the AttachmentNames property.
def Message saveAttachments(Message message) {
// Store attachments in property
def attachments = new HashMap<String, DataHandler>(message.getAttachments())
message.setProperty('AttachmentsMap', attachments)
// Store the number of attachments in property
message.setProperty('AttachmentCount', attachments.size())
// Clear the attachments
message.getAttachments().clear()
message.getAttachmentWrapperObjects().clear()
// Get original attachment names and store as hashmap in property
def jsonAttList = message.getHeader('XI_AttachmentList', String)
def attachmentNames = [:]
if (jsonAttList != null) attachmentNames = new JsonSlurper()?.parseText(jsonAttList)
message.setProperty('AttachmentNames', attachmentNames)
return message
}All attachments are then processed using a Looping Process Call.
Receiver iFlow - Transfer attachments to an SFTP server
In this Local Integration Process, the current attachment in the loop is read and its contents are written to the message body. The AttachmentNames property is then used to determine the original file name, which is stored in the SFTP_FileName property. The Local Integration Process responsible for transferring the data to the SFTP server can then retrieve the file name from this property.
def Message getNextAttachment(Message message) {
def attachments = message.getProperty('AttachmentsMap')
if (attachments.isEmpty()) {
message.setProperty('AttachmentCount', 0)
return message
}
// Get next attachment to be processed
def nextAttch = attachments.keySet().iterator().next()
// Remove the attachment from the Map
def attachment = attachments.remove(nextAttch)
//def attachmentName = attachment?.getName()?:"attachment_"+attachments.size()
def attachmentNames = message.getProperty('AttachmentNames')
def attachmentName = attachmentNames[attachment?.getName()?:"attachment_"+attachments.size()]?:attachment?.getName()?:"attachment_"+attachments.size()
// Replace the message with attachment
message.setBody(attachment.getContent())
// Update the AttachmentCount
message.setProperty('AttachmentCount', attachments.size())
message.setProperty('SFTP_FileName', attachmentName)
return message
}
The XI Sender Adapter provides all the necessary information in the form of an XML manifest. Using this information alongside a short Groovy script enables us to easily save and reuse the original names of the attachments.
Useful links XI Sender Adapter:
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
| User | Count |
|---|---|
| 12 | |
| 10 | |
| 9 | |
| 8 | |
| 8 | |
| 6 | |
| 4 | |
| 4 | |
| 4 | |
| 3 |