Adapter Module: ReplaceString
Adapter Module: ReplaceString
Use
ReplaceString module is used to replace strings within the xml text of the payload of the message. It can be used to modify the message structure before it gets in PI and also before it gets out. It is useful when there are strings like namespaces, types, etc that PI can’t “understand” or that PI is unable to include in outbound messages.
The module obtains a pair of string : <OldString>;<NewString> as parameter. Reads the payload of the message and converts it to a string. Then it searches <OldString> within the message and replace each occurrence with <NewString>. Finally it returns the message modified. If OldString can’t be found in the message nothing is modified and the original message is returned.
The module accepts multiple string pairs to allow multiple replacements in a single execution.
Deployment
Enterprise Java Bean Project: ReplaceString-ejb
Enterprise Java Bean Application: ReplaceString-ear
Integration
The module can be used in any kind of Sender and Receiver adapters.
Activities
This section describes all the activities that have to be carried out in order to configure the module.
Entries in processing sequence
Insert the module in front of the adapter module as shown in the picture below.
Entries in the module configuration
The table below shows the possible parameters and values of the adapter module.
Parameter | Type | Possible values | Description | Example |
---|---|---|---|---|
separator | Optional |
Any alphanumeric character. Default value: ‘|’ |
This parameter configures the character that separates a pair of strings. |
separator = ‘;’ |
param(*) |
Mandatory |
Pair of strings separated by “separator” character. <OldString><separator>[<NewString>, blankString,emptyString]. Default value: none. |
This parameter is used to obtain Old string to be replaced by the New string in the message. The pair is separated by “separator”. As Adapter module tab in PI trims strings by the right side, if it’s needed to replace OldString by a blank string or an empty string, the parameters blankString or emptyString have to be used. |
param1=str1;str2 param2=str1;blankString param3=str1;emptyString |
(*) The parameter name can be any string: param1, parameter, aaa, etc.
Example
Payload data before execution:
<MT_ROOT>
<RECORDSET>
<DATA>
<FIELD1>value</FIELD1>
<FIELD2>value</FIELD2>
<FIELD3>value</FIELD3>
</DATA>
<DATA>
<FIELD1>value</FIELD1>
<FIELD2>value</FIELD2>
<FIELD3>value</FIELD3>
</DATA>
</RECORDSET>
</MT_ROOT>
Module parameters:
separator = ‘;’
param1 = ’RECORDSET;ROW’
param2 = <MT_ROOT>;<MESSAGE type=TYPE>
param3 = </MT_ROOT>;</MESSAGE>
param4 = <DATA>;emptyString
param5 = </DATA>;emptyString
Payload data after execution:
< MESSAGE type=TYPE>
<ROW>
<FIELD1>value</FIELD1>
<FIELD2>value</FIELD2>
<FIELD3>value</FIELD3>
<FIELD1>value</FIELD1>
<FIELD2>value</FIELD2>
<FIELD3>value</FIELD3>
</ROW>
</MESSAGE>
ReplaceStringBean
/**
*
*/
package com.arms.integrations;
import java.rmi.RemoteException;
import javax.ejb.EJBException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
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.engine.interfaces.messaging.api.Message;
import com.sap.aii.af.service.auditlog.Audit;
import com.sap.engine.interfaces.messaging.api.MessageKey;
import com.sap.engine.interfaces.messaging.api.auditlog.AuditLogStatus;
import java.io.*;
import java.util.*;
/**
* @author Roger Allué i Vall
*
*/
public class ReplaceStringBean implements SessionBean, Module {
private SessionContext myContext;
private static final String C_SEPARATOR_STRING = "separator";
private static final String C_AUX_SEPARATOR = "|";
private static final String C_MODULEKEY_STRING = "module.key";
private static final String C_BLANK_STRING = "blankString";
private static final String C_EMPTY_STRING = "emptyString";
/* (non-Javadoc)
* @see javax.ejb.SessionBean#ejbActivate()
*/
public void ejbActivate() throws EJBException, RemoteException {
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see javax.ejb.SessionBean#ejbPassivate()
*/
public void ejbPassivate() throws EJBException, RemoteException {
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see javax.ejb.SessionBean#ejbRemove()
*/
public void ejbRemove() throws EJBException, RemoteException {
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see javax.ejb.SessionBean#setSessionContext(javax.ejb.SessionContext)
*/
public void setSessionContext(SessionContext arg0) throws EJBException,
RemoteException {
// TODO Auto-generated method stub
myContext = arg0;
}
/* (non-Javadoc)
* @see javax.ejb.SessionSynchronization#afterBegin()
*/
public void afterBegin() throws EJBException, RemoteException {
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see javax.ejb.SessionSynchronization#afterCompletion(boolean)
*/
public void afterCompletion(boolean arg0) throws EJBException,
RemoteException {
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see javax.ejb.SessionSynchronization#beforeCompletion()
*/
public void beforeCompletion() throws EJBException, RemoteException {
// TODO Auto-generated method stub
}
public void ejbCreate() throws javax.ejb.CreateException {
}
public ModuleData process(ModuleContext moduleContext, ModuleData inputModuleData) throws ModuleException {
ByteArrayOutputStream payloadOut = null;
int inputByte = 0;
String payloadStr = "";
Message msg;
Enumeration paramList;
String sep, paramKey,param, strArray[];
try {
msg = (Message) inputModuleData.getPrincipalData();
MessageKey amk = new MessageKey(msg.getMessageId(), msg.getMessageDirection());
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,".-Module beginning");
payloadStr = new String(msg.getDocument().getContent(),"UTF-8");
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,"Payload before execution:" );
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,payloadStr );
sep = moduleContext.getContextData(C_SEPARATOR_STRING);
if ( sep == null ) {
sep = C_AUX_SEPARATOR;
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,"Default separator used: " + sep);
}
else {
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,"Separator found: " + sep);
}
paramList = moduleContext.getContextDataKeys();
while( paramList.hasMoreElements()) {
paramKey = (String) paramList.nextElement();
//Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,"ParamKey: " + paramKey);
param = moduleContext.getContextData(paramKey);
//Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,"Param: " + param);
if ( ! paramKey.equals(C_SEPARATOR_STRING) && ! paramKey.equals(C_MODULEKEY_STRING) ){
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,"ParamKey: " + paramKey);
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,"Parameter: " + param);
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,"Separator: " + sep);
strArray = param.split(sep);
if (strArray != null){
if ((! strArray[0].equals(null)) && (! strArray[1].equals(null))){
if ( strArray[1].equals(C_BLANK_STRING)){
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,"Blank String");
strArray[1]=" ";
}
else if (strArray[1].equals(C_EMPTY_STRING)){
strArray[1]="";
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,"Empty String");
}
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,"Substitution strArray[0]: " + strArray[0]);
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,"Substitution strArray[1]: " + strArray[1]);
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,"Substitution payloadStrA : " + payloadStr);
payloadStr = payloadStr.replaceAll(strArray[0],strArray[1]);
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,"Substitution payloadStrB : " + payloadStr);
}
}
}
}
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,"Payload after replacement:" );
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,payloadStr );
payloadOut = new ByteArrayOutputStream();
payloadOut.write(payloadStr.getBytes());
msg.getDocument().setContent(payloadOut.toByteArray());
inputModuleData.setPrincipalData(msg);
}
catch(Exception e) {
ModuleException me = new ModuleException(e);
throw me;
}
return inputModuleData;
}
}
Hi Roger,
Thanks for sharing this Generic adapter module solution for replacing the string.. 🙂
Thanks,
Srikanth
Hi Roger,
😀 I really like your adapter module series.
Thanks
VB
Thank you Vladimir!
Hi Roger,
The blog helped me a lot in developing a module.
Thanks
Somil.
Hi Somil,
I'm very pleased you found it useful.
Thanks and Regards!
Hello Aaron,
It seems something's wrong with your ear. Could you please tell me how are you deploying you ear? You can check if the module is correctly deployed from the perspective Deployment of NWDS. On left panel you should see that the module is running (in green).
Regards !
Take a look to this document of William Li. It has an step by step of how to develop and deploy an adapter module:
http://scn.sap.com/docs/DOC-3816
Regards
Hi, I think the Ear is deployed OK now. However in RWB com channel monitor I get error: "Message processing failed. Cause: java.lang.ArrayIndexOutOfBoundsException: 1"
Does this perhaps mean my module parameters are maybe wrong? I tried with single quotes around the replace strings also. Is there a way to set breakpoint in NWDS or simulate the execution at runtime?
Think I found the issue here is with the separator. If I use ';' in the config I get ArrayIndexOutOfBounds, but if I do not quote it in config it sends the message through the module OK.
I had only one other issue but solved it myself.
On lines 137, 138 Java replaceAll was not making the substitution until I converted the String Arrays to regular Strings using .toString() method. After that with the same parameters it started to work. This could maybe be down to our old java version? I saw from your document you use the latest and greatest.
Thanks so much Roger for posting this document and giving me some hints!!! I now have a basic understanding of how to write and deploy adapter modules.
Hello Aaron,
Please, check the following pdf I created with all the screen shots of the whole process (creation/configuration, deployment, execution).
https://dl.dropboxusercontent.com/u/25955636/SAP%20PI%20Module.pdf
Let me know if you find it useful.
Regards!
Hi Roger
I am doing a REST --- PI --- ECC synchronous scenario. The xml payload is converted to JSON by REST channel and this causes an unwanted backward slash to be added to the payload.
like this
http://host:port/RESTAdapter/xyz
becomes
http:\/\/host:port\/RESTAdapter\/xyz
is there any module that can remove the backward slash from the target payload
Regards
Midhun
Hello Midhun,
Sorry, but I didn't use the REST Adapter yet. However, If the URL is added to the payload you can always use my module.
Just to understand a bit more the problem, could you please paste an example of your payload?
Regards!
Hi Roger
This is the payload:
{
"MT_EmployeeNumber_CRN_Response_Receiver": {
"EmployeeNumberTable": [
{
"EmployeeNumber": "134541",
"EmployeeName": Mark,
"EmployeeDescription": "TEST 123",
"EmployeeGroup": "SAP",
"links": {
"self": {
"href":
"http:\/\/host:port\/RESTAdapter\/CRN\/EmployeeNumber\/134541"
}
}
},
]
}
}
This payload is converted from xml to JSON. When the conversion is done an additional backward slash is added in front of all the forward slashes in the url.
This is a synchronous scenarios, REST adapter at sender side and SOAP adapter at receiver side.
My requirement is to take the backward slash from the JSON Payload
Midhun
Hello Midhun,
My module ReplaceString treats the payload as text, so it shouldn't distinguish between xml and json messages. Notice that the module uses the java method replaceAll. This method expects a regular expression and you should escape backslashes.
Could you configure the module with the following parameters?
param1=\\/;/
separator=;
Regards!
Look, I have created a simple class to test replaceAll method:
And this is the result with your original string and the parameters I mentioned above:
Regards!
Thanks a lot for your time, Roger
I am working on it and will update you. Your help was priceless and really appreciate the time you have taken to help me
You are welcome!
Let me know if you managed. 😉
Hello Roger,
Did you test your module with the REST adapter, Can you please share your experience.
Even I am facing issues handling backslash in xml to JSON conversion.
So would like to hear from you.
Regards,
Sushant
Hi Roger,
Thank you for sharing your module. I think this will be helpful in many scenarios.
However, I have some doubts about following piece of code:
while((inputByte = payloadIn.read()) != -1) {
payloadStr = payloadStr + (char) inputByte;
}
As payloadIn.read() provides a byte, and you change it to (char), this leads to errors for all multi-byte UTF-8 character. For example the German character 'ä' (hex c3 a4) would be transformed to 'ä'
I recommend using following code line:
payloadStr = new String(msg.getDocument().getContent(),"UTF-8");
This is the proper way to tranform a byte array into a String.
Regards
Stefan
Thank you Stefan!!
I will update the code right now.
Regards!
Hello Apu,
there are plenty of blogs on how to create your own module. Don't get me wrong, but there's a difference between sharing knowledge and working for free.
Good luck.
Hi,
I have a problem with conversion on REST Receiver adapter, method GET. The communication channel is not converting the json payload to html, so the payload that returns to S4Hana is empty.
The interface is sycronous: SAP S4Hana (Proxy) -> SAP PO (REST) -> External System (GET method).
Here is the json returned from external system.
{“Details”:{“OrderDetails”:[{“OrderID”:”0″,”OrderNo”:”00010″ }],”CustomerID”:”00001″,”UserID”:””,”Name”:””,”PCode”:””,”Status”:”Sucess”,”Message”:”Record Inserted Successfully”}}
Here is the error message from PO system.
MP: exception caught with cause com.sap.aii.adapter.rest.ejb.parse.InvalidJSonContent: Invalid JSON message content used; Message: “JSONArray text must end with ] at character 28 of
Bellow are the images.
Channel
Error Message
Please suggest me to resolve this error.
Regards
Surender
Hello everybody
I'm trying to implement the Adapter Module ReplaceString for PI 7.4. However
the class com.sap.aii.af.service.auditlog.Audit cannot be imported as it does not
exists anymore. I didn't find the JAR-File anymore on the OS of the PI.
How to proceed in order to be able to use the Audit class?
Thanks for your feedback.
Kind regards
Chris
Hello Chris,
The audit statements are just to trace the module execution and see what is doing in the channel monitoring log. If you want you can ommit them.
Regards!
Hello Roger
thanks for your feedback.
I've found now a way to access the audit classes:
In https://blogs.sap.com/2016/04/16/make-your-custom-adapter-module-a-little-more-flexible/
the classes com.sap.engine.interfaces.messaging.api.auditlog.AuditAccess and
com.sap.engine.interfaces.messaging.api.auditlog.AuditLogStatus are used
to define the audit variable. So I didn't have to comment the audit statements (because
they are still important for the trace messages to me).
I've deployed then the ReplaceString EAR according to the following link:
https://blogs.sap.com/2015/01/29/create-sap-pi-adapter-modules-in-ejb-30-standard/
After the deployment I can see 4 objects in the JNDI browser (like in
https://blogs.sap.com/2017/05/29/adapter-module-development-in-nwdi/ )
Unfortunately the adapter module ReplaceString does not seem to get called
(I've inserted ReplaceString before the CallSapAdapter, like shown in this blog).
In the message protocoll I see only:
"MP: processeing local module localejbs/CallSapAdapter" but I cannot find
"messages like ".-Module beginning" .
Somehow it seems that the module is not being called even though I've could successully
deploy it and it can be seen in the JNDI-Browser.
Do you have any clue how I can remedy to this problem?
Thanks for your feedback.
Kind regards
Christof
PS: the current views in the EJB explorer and the JNDI explorer:
PS2: I've found a difference in the EJB explorer between ReplaceStringBean and PlainConverterModule: the status is different for "com.sap.aii.af.lib.mp.module.Module:
=> could this be the reason that ReplaceString module is not being called? How to remedy?
PS3: when I execute the test function in the EJB explorer I get the following error message for ReplaceString:
.........
Caused by: javax.ejb.EJBTransactionRolledbackException: ASJ.ejb.005044 (Failed in component: sap.com/ReplaceStr_EAR)
Exception raised from invocation of public com.sap.aii.af.lib.mp.module.ModuleData com.sap.pi.ReplaceString.process(
com.sap.aii.af.lib.mp.module.ModuleContext,com.sap.aii.af.lib.mp.module.ModuleData)
throws com.sap.aii.af.lib.mp.module.ModuleException method on bean instance com.sap.pi.ReplaceString@52332ad1
for bean sap.com/ReplaceStr_EAR*annotation|ReplaceStr_EJB.jar*xml|ReplaceStringBean in application
sap.com/ReplaceStr_EAR.; nested exception is: java.lang.NullPointerException: while trying to invoke
the method com.sap.aii.af.lib.mp.module.ModuleData.getPrincipalData() of a null object loaded from
local variable 'inputModuleData'; nested exception is: javax.ejb.EJBException: ASJ.ejb.005044
(Failed in component: sap.com/ReplaceStr_EAR) Exception raised from invocation
of public com.sap.aii.af.lib.mp.module.ModuleData com.sap.pi.ReplaceString.process(com.sap.aii.af.lib.mp.module.ModuleContext,
com.sap.aii.af.lib.mp.module.ModuleData) throws com.sap.aii.af.lib.mp.module.ModuleException method on bean
instance com.sap.pi.ReplaceString@52332ad1 for bean sap.com/ReplaceStr_EAR*annotation|ReplaceStr_EJB.jar*xml|
ReplaceStringBean in application sap.com/ReplaceStr_EAR.; nested exception is: java.lang.NullPointerException:
while trying to invoke the method com.sap.aii.af.lib.mp.module.ModuleData.getPrincipalData() of a null
object loaded from local variable 'inputModuleData'
.......
However the inputModuleData variable is instanciated like in your code above:
....
@Override
public ModuleData process(ModuleContext moduleContext,
ModuleData inputModuleData) throws ModuleException {
Message message = (Message) inputModuleData.getPrincipalData();
MessageKey messageKey = message.getMessageKey();
.......
Any clue on how to remedy on that?
Hello Roger
the problem is solved: everythin was correct with the EJB Explorer.
I didn't properly call the ReplaceString Module in the sender adapter, thats
why it didn't get called. Now it's working.
However I have a question regarding your statement above:
"
while((inputByte = payloadIn.read()) != –1) {
payloadStr = payloadStr + (char) inputByte;
}
"
You replaced it by
"
payloadStr = new String(msg.getDocument().getContent(),”UTF-8″);
"
However this statement uses the SAXParser and it throws an expection when e.g.
I want to get rid of a @ (by replacing @ by emptystring) in REST response message
where the JSON-Structure is converted to an XML structure.
So in this case it would be better to have your piece of code (no SAXParser involved)
than the suggested statement by Stefan Grube (knowing that in this particular case
the problem with the Umlaute ä can arise).
Could you just share how in your example with
"
while((inputByte = payloadIn.read()) != –1) {
payloadStr = payloadStr + (char) inputByte;
}
"
you did define and initialize payloadIn (with class Inputstream or other)?
Thanks for your feedback.
Kind regards
Chris
Hi Roger Allue ,
Curious to know if this module helps to remove white spaces in the XML tags .
Source XML tags look like below where you see a space between Exchange and data, Value and data, We are not able to create a datatype since it has a space.
<Exchange data >12344</Exchange data>
<Value data>xyz<Value data>