Adapter module: Set the file name from Payload using Regular Expression (Regex).
Hi friends,
I got a business requirement where SAP ECC is sending data using outbound ABAP Proxy to PI and PI is receiving the same and creating the file at FTP/SFTP server (Proxy to file scenario). File name is supplied in the payload which is coming from Sender SAP system. To set filename in Dynamic Configuration (ASMA), I used XSLT mapping (and there were few transformations also). Variable Substitution didn’t work for me as the file name was not available until Receiver File/SFTP adapter (during transformation it was ignored, it was our business requirement)
I got to develop many interfaces which using the above mentioned flow, and I was writing the same XSLT code (to set the file name in dynamic configuration) in all the mapping so I decided to write the adapter module which perform the same task that XSLT code was doing. Once Adapter module is deployed, it can be used in any Communication Channel and any team member can reuse it just by supplying necessary configuration parameters.
This adapter module is developed using Enterprise Java Beans (EJB) 3.0. It expects the File Name XPath to be provided in adapter configuration. This XPath is used to get the file using regular expression (regex). The same implementation could be done using DOM parser too but DOM parser was performing slower for large files, so I am using regex which is better option as it is native to java.
This adapter module can be used in Sender or Receiver communication channel before “CallSapAdapter” module. I am using it in SOAP (XI3.0) ABAP Proxy Communication Channel.
Below is the java code for adapter module: As I am using EJB3.0, it is not necessary to implement Session bean. Only Module interface is required to implement.
Method getRegexFromXPath() returns the regular expression which is used in Bean class to match the pattern and find out the file name.
package com.sap.pi.adapter.module;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.Local;
import javax.ejb.LocalHome;
import javax.ejb.Remote;
import javax.ejb.RemoteHome;
import javax.ejb.Stateless;
import com.sap.aii.af.lib.mp.module.Module;
import com.sap.aii.af.lib.mp.module.ModuleContext;
import com.sap.aii.af.lib.mp.module.ModuleData;
import com.sap.aii.af.lib.mp.module.ModuleException;
import com.sap.aii.af.lib.mp.module.ModuleHome;
import com.sap.aii.af.lib.mp.module.ModuleLocal;
import com.sap.aii.af.lib.mp.module.ModuleLocalHome;
import com.sap.aii.af.lib.mp.module.ModuleRemote;
import com.sap.engine.interfaces.messaging.api.Message;
import com.sap.engine.interfaces.messaging.api.MessageKey;
import com.sap.engine.interfaces.messaging.api.MessagePropertyKey;
import com.sap.engine.interfaces.messaging.api.PublicAPIAccessFactory;
import com.sap.engine.interfaces.messaging.api.XMLPayload;
import com.sap.engine.interfaces.messaging.api.auditlog.AuditAccess;
import com.sap.engine.interfaces.messaging.api.auditlog.AuditLogStatus;
import com.sap.engine.interfaces.messaging.api.exception.MessagingException;
/**
* Session Bean implementation class FileSetProperties
*/
@Stateless(name = “FileSetPropertiesBean”)
@Local(value = { ModuleLocal.class })
@Remote(value = { ModuleRemote.class })
@LocalHome(value = ModuleLocalHome.class)
@RemoteHome(value = ModuleHome.class)
public class FileSetProperties implements Module {
private AuditAccess audit;
public FileSetProperties() {
}
@Override
public ModuleData process(ModuleContext moduleContext, ModuleData inputModuleData) throws ModuleException {
FunctionsLibrary functionLibrary = new FunctionsLibrary();
Object obj = null;
Message msg = null;
MessageKey msgKey = null;
String fileName = “”;
String fileXPath = “FileXPath”;
try {
obj = inputModuleData.getPrincipalData();
msg = (Message) obj;
msgKey = new MessageKey(msg.getMessageId(), msg.getMessageDirection());
addLog(msgKey, AuditLogStatus.SUCCESS, “***** Entering in FileSetProperties adapter module ******”);
MessagePropertyKey fileKey = new MessagePropertyKey(“FileName”, “http://sap.com/xi/XI/System/File“);
XMLPayload xmlPayload = msg.getDocument();
InputStream inputStream = xmlPayload.getInputStream();
String inString = functionLibrary.inputStreamToString(inputStream);
String regex = functionLibrary.getRegexFromXPath(moduleContext.getContextData(fileXPath));
addLog(msgKey, AuditLogStatus.SUCCESS, “Regular Expression: “ + regex + ” Input message parsing is done.”);
// addLog(msgKey, AuditLogStatus.SUCCESS, “Input message parsing is done. “);
if (inString.matches(regex)) {
fileName = inString.replaceAll(regex, “$” + functionLibrary.getRegexReplacementCount());
addLog(msgKey, AuditLogStatus.SUCCESS, “Supplied XPath in Communication Challel is: “ + moduleContext.getContextData(fileXPath) + “. “ + “File name from payload is: “ + fileName + ” Going to set this file name in ASMA”);
inputModuleData.setPrincipalData(msg);
} else {
addLog(msgKey, AuditLogStatus.WARNING, “Input message does not matches with regex . “ + regex);
fileName = functionLibrary.getDefaultFileName();
addLog(msgKey, AuditLogStatus.SUCCESS, “Using default file Name “ + fileName);
}
if (!“”.equals(fileName)) {
msg.setMessageProperty(fileKey, fileName);
addLog(msgKey, AuditLogStatus.SUCCESS, “File name has been set in ASMA. You can access it from Dynamic Configuration.” );
addLog(msgKey, AuditLogStatus.SUCCESS, “***** Exiting from FileSetProperties adapter module ******”);
}
} catch (Exception e) {
// e.printStackTrace();
addLog(msgKey, AuditLogStatus.ERROR, e.getMessage().toString());
addLog(msgKey, AuditLogStatus.ERROR, “***** Error occured. Exiting from FileSetProperties adapter module ****** “);
try {
handleException(msgKey, e);
} catch (Exception e1) {
}
}
//addLog(msgKey, AuditLogStatus.SUCCESS, “***** Exiting from FileSetProperties adapter module ******”);
return inputModuleData;
}
@PostConstruct
public void initialiseResource() {
try {
audit = PublicAPIAccessFactory.getPublicAPIAccess().getAuditAccess();
} catch (MessagingException e) {
System.out.println(“WARNING: Audit log not available in standalone testing”);
}
}
@PreDestroy
public void ReleaseResource() {
}
private void addLog(MessageKey msgKey, AuditLogStatus status, String message) {
if (audit != null) {
audit.addAuditLogEntry(msgKey, status, message);
} else {
System.out.println(“Audit Log: “ + message);
}
}
private void handleException(MessageKey msgKey, Exception e) throws Exception {
// throw new Exception(comment + ” ” + getStackTraceAsString(e), e);
addLog(msgKey, AuditLogStatus.ERROR, getStackTraceAsString(e));
}
public String getStackTraceAsString(Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String stacktrace = sw.toString();
return stacktrace;
}
}
FileSetProperties class uses getRegexFromXPath() which is written in FunctionsLibrary class.
package com.sap.pi.adapter.module;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Date;
public class FunctionsLibrary {
int replacementCount = 3;
public String getRegexFromXPath(String XPath) {
StringBuffer sb = new StringBuffer();
try {
if (XPath != null) {
XPath = XPath.replace(“//”, “”);
if (XPath.length() > 0) {
sb.append(“(?s)”);
String arg[] = XPath.split(“/”);
for (int i = 0; i < arg.length; i++) {
if (arg[i].length() > 0) {
sb.append(“(.*?)(“);
sb.append(arg[i]).append(“)”);
replacementCount = replacementCount + 2;
if (i == arg.length – 1) {
sb.append(“(.*?)(>)(.*?)(<)(.*?)(“).append(arg[i]).append(“>)(.*)”);
}
}
}
} else {
}
}
} catch (Exception e) {
}
return sb.toString();
}
public int getRegexReplacementCount() {
return this.replacementCount;
}
public String inputStreamToString(InputStream in) {
// read in stream into string.
StringBuffer buf = new StringBuffer();
try {
InputStreamReader isr = null;
// try UTF-8 conversion
try {
isr = new InputStreamReader(in, “UTF-8”);
} catch (UnsupportedEncodingException e) {
// or atleast in natural encoding
isr = new InputStreamReader(in);
}
int c = 0;
while ((c = isr.read()) != -1) {
buf.append((char) c);
}
in.close();
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
return buf.toString();
}
public String getDefaultFileName() {
Format formatter = new SimpleDateFormat(“yyyyMMdd-hhmmss”);
Date d = new Date();
String fileName = “Unknown_” + formatter.format(d) + “.xml”;
return fileName;
}
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
String XPath = “//text/text1/FileName”;
FunctionsLibrary functionLibrary = new FunctionsLibrary();
System.out.println(functionLibrary.getRegexFromXPath(XPath));
// System.out.println(functionLibrary.getDefaultFileName());
System.out.println(functionLibrary.getRegexReplacementCount());
}
}
Use FileSetPropertiesBean in ejb-j2ee-engine.xml and assign JNDI name. JNDI name is used in Communication Channel in Module tab.
<?xml version=“1.0” encoding=“UTF-8”?>
<ejb-j2ee-engine xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance“ xsi:noNamespaceSchemaLocation=“ejb-j2ee-engine_3_0.xsd”>
<enterprise-beans>
<enterprise-bean>
<ejb-name>FileSetPropertiesBean</ejb-name>
<jndi-name>FileSetProperties</jndi-name>
</enterprise-bean>
</enterprise-beans>
</ejb-j2ee-engine>
Edit application-j2ee-engine.xml in EAR project and use the below xml
<?xml version=“1.0” encoding=“UTF-8”?>
<application-j2ee-engine xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance“ xsi:noNamespaceSchemaLocation=“application-j2ee-engine.xsd”>
<reference reference-type=“hard”>
<reference-target target-type=“service” provider-name=“sap.com”>engine.security.facade</reference-target>
</reference>
<reference reference-type=“hard”>
<reference-target target-type=“library” provider-name=“sap.com”>engine.j2ee14.facade</reference-target>
</reference>
<reference reference-type=“hard”>
<reference-target target-type=“service” provider-name=“sap.com”>com.sap.aii.af.svc.facade</reference-target>
</reference>
<reference reference-type=“hard”>
<reference-target target-type=“interface” provider-name=“sap.com”>com.sap.aii.af.ifc.facade</reference-target>
</reference>
<reference reference-type=“hard”>
<reference-target target-type=“library” provider-name=“sap.com”>com.sap.aii.af.lib.facade</reference-target>
</reference>
<reference reference-type=“hard”>
<reference-target target-type=“library” provider-name=“sap.com”>com.sap.base.technology.facade</reference-target>
</reference>
<fail-over-enable xsi:type=“fail-over-enableType_disable”
mode=“disable” />
</application-j2ee-engine>
Hi Sandeep
Can you provide some sample test data and testing results? And also maybe elaborate on how the getRegexFromXPath works? At the moment it's hard to visualize the use case for this module and how the regex part works.
Rgds
Eng Swee
Hi Eng Swee,
Below is Sample XML, which is input to the Adapter module.
<?xml version="1.0" encoding="utf-8"?>
<Data>
<ArticleInfo>
<Data>Article1</Data>
<Data>Article2</Data>
<Data>Article3</Data>
</ArticleInfo>
<FileName>List_Invoice_2015.txt</FileName>
</Data>
In this example, we are creating file using file name List_Invoice_2015.txt.
XPath supplied in Communication channel: //FileName
getRegexFromXPath will return: (?s)(.*?)(FileName)(.*?)(>)(.*?)(<)(.*?)(FileName>)(.*)
It is regular expression to parse the input data.
There is another method: getRegexReplacementCount(), it gives the counter to replace the string which is used in replaceAll method.
Output of replaceAll is fileName which we are setting in Dynamic Configuration.
You need to use "Adapter Specific Message Attributes" and "FileName" option in FIle/SFTP adapter.
Please let me know if you have any question.
Thanks,
Sandeep Maurya
Hi Sandeep,
This is a great blog especially when we want to control the file name creation on the source side.
Regards,
Sameer
Thanks Sameer.
Sandeep,
Many thanks for sharing this information!
Regards,
Kirk
Thanks Kirk.
Nice work.
Have you looked at this -> Dynamic File Name usign adapter specific message attribute
Hi Prabhu,
You can not use this Adapter module for above mentioned thread because,
1. Your file name is custom generated, <WareHouseNumber_Timestamp>_WO.<EXT> . This Adapter module expects file name in incoming payload and provide XPath of file name.
2. I wont suggest you to write adapter module for specific scenario as it requires more development effort than writing java mapping.
Follw the steps suggested by Veerendra Kumar Mamidi.
Please let me know if you have any questions.
Thanks,
Sandeep Maurya