Send File to Two Different Locations using Adapter Module
Many a times we have a requirement where the client wants that any file which is send to external system should be archived on local file system/ FTP folder.
To take a backup of target file we have few alternatives available like we can opt a conventional way by adding one more receiver and assign one file receiver channel to it and then simultaneously send the message to third party as well as to the client’s local system (let it be NFS or FTP), OS commands can also be one of the alternative or we can write a adapter module to accomplish this requirement.
Recently I also came across the same situation, where SAP system is generating a message and after doing few transformations in PI I need to send transformed XML file message to external system and take a backup of the same target file on local FTP server.
Firstly I thought of adding one more “Business Component” (re-use the same mapping) and file receiver channel pointing to Local FTP folder in the existing scenario, but as expected client starts asking the same solution for multiple file interfaces 🙂 . So to have a reusable kind of solution I created one generic adapter module which will create a backup of target message on local server and then send the same to external application.
So, the objective of this blog is to show how the target file message can be send to two different locations using one file receiver adapter.
Below module code has been defined in such a way that depending upon the parameters passed from channel it can back up a target message either on FTP or NFS server
package com.poc.sdn;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.rmi.RemoteException;
import javax.ejb.EJBException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.ejb.TimedObject;
import javax.ejb.Timer;
import sun.net.TelnetOutputStream;
import sun.net.ftp.FtpClient;
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.service.auditlog.Audit;
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.XMLPayload;
import com.sap.engine.interfaces.messaging.api.auditlog.AuditLogStatus;
/*\*
\* @author amitsrivastava5
\*
*/
public class BackUpFilesBean implements SessionBean, TimedObject {
/\* (non-Javadoc)
\* @see javax.ejb.SessionBean#ejbActivate()
*/
@Override
public void ejbActivate() throws EJBException, RemoteException {
// TODO Auto-generated method stub
}
/\* (non-Javadoc)
\* @see javax.ejb.SessionBean#ejbPassivate()
*/
@Override
public void ejbPassivate() throws EJBException, RemoteException {
// TODO Auto-generated method stub
}
/\* (non-Javadoc)
\* @see javax.ejb.SessionBean#ejbRemove()
*/
@Override
public void ejbRemove() throws EJBException, RemoteException {
// TODO Auto-generated method stub
}
/\* (non-Javadoc)
\* @see javax.ejb.SessionBean#setSessionContext(javax.ejb.SessionContext)
*/
@Override
public void setSessionContext(SessionContext arg0) throws EJBException,
RemoteException {
// TODO Auto-generated method stub
}
/\* (non-Javadoc)
\* @see javax.ejb.TimedObject#ejbTimeout(javax.ejb.Timer)
*/
@Override
public void ejbTimeout(Timer arg0) {
// TODO Auto-generated method stub
}
public void ejbCreate() throws javax.ejb.CreateException {
}
public ModuleData process(ModuleContext mc, ModuleData inputModuleData)
throws ModuleException {
Object obj = null;
Message msg = null;
MessageKey amk = null;
//Reading Type of Transport Protocol
String ServerType = (String) mc.getContextData("Server");
try {
// Retrieves the current principle data, usually the message , Return type is Object
obj = inputModuleData.getPrincipalData();
// A Message is what an application sends or receives when interacting with the Messaging System.
msg = (Message) obj;
// MessageKey consists of a message Id string and the MessageDirection
amk = new MessageKey(msg.getMessageId(),msg.getMessageDirection());
//Reading file name from message header
MessagePropertyKey mpk = new MessagePropertyKey("FileName","http://sap.com/xi/XI/System/File");
String filename = msg.getMessageProperty(mpk);
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "filename is" \+filename );
// Returns the main document as XMLPayload.
XMLPayload xpld = msg.getDocument();
byte\[\] inpbyt = xpld.getContent();
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "Input file read successfully");
//Archiving target file on FTP server
if (ServerType.equals("FTP"))
{
String HostName = (String) mc.getContextData("HostName");
String FTPDirectory = (String) mc.getContextData("FTPDirectory");
String Username = (String) mc.getContextData("Username");
String Password = (String) mc.getContextData("pwd");
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "Connecting to FTP location");
FtpClient client = new FtpClient();
client.openServer(HostName);
client.login (Username,Password);
client.cd(FTPDirectory);
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "Connection to FTP location Successful");
TelnetOutputStream out = client.put("Backup_"+filename);
out.write(inpbyt);
out.flush();
out.close();
client.closeServer();
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "File written sucessfully");
}
//Archiving target file on SAP file system
else if (ServerType.equals("NFS"))
{
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "Write file on NFS location");
String NFSDirectory = (String) mc.getContextData("NFSDirectory");
File path = new File(NFSDirectory+"/" +filename);
FileOutputStream fos = new FileOutputStream(path);
fos.write(inpbyt);
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "File written sucessfully")
}
else
{
throw new CustomException("ServerType Parameter Is Not Having Valid Value");
}
// Set content as byte array into payload
xpld.setContent(inpbyt);
// Sets the principle data that represents usually the message to be processed
inputModuleData.setPrincipalData(msg);
}catch (Exception e) {
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS,
"Module Exception caught:");
ModuleException me = new ModuleException(e);
throw me;
}
return inputModuleData;
}
}
class CustomException extends Exception
{
public CustomException(String message)
{
super(message);
}
}
Module configuration to Backup file on FTP server
If target file needs to be archived on FTP server then below module parameters need to be passed in file receiver channel
Note – “Server” parameter value (“FTP” or “NFS”) will call the corresponding method in module to archive files on FTP or NFS server.
Hi Amit, I like your idea, may be it will better if you extend the funcionality including the possibility of N receivers, i dont know if it is possible to pass an array variable like a parameter ¿?. Sometimes the receivers grow exponentially and to manage all in one receiver channel seems a great idea, in my opinion.
Thanks for sharing!.
Hello Amit,
Thanks for sharing. New solution for this known situation!
Diego
Its a very excellent achievement and very helpful in various live scenarios.
Thanks.
Great Blog. It will be a reference for many who may face this issue.
Thanks for sharing it !!
Hello,
what if the reciever channel runs on an error (e.g. temporary file system / FTP problem) - I assume this module will be executed anyway, so the file will be copied on the FTP server? If it comes out that there was e.g. a configuration error in the receiver channel and the process should stopped as the message should not be sent - we cannot cancel / undo this module step?
And when there are some retries of the file receiver channel - will the file module be executed more than once, tryin to copy the file to FTP several times?
Thanks,
Christoph
Nice Blog Amit
Regards
Vinay
valid question from Christoph. I will advice to check the behaviour before implementing calls externally sitting within a module.
I think an alternative option is to implement a script to distribute the file to multiple sources.
Amit,
Nice blog dude 🙂
Regards,
Pranil.
Good Job Amit...
Hello All,
Thank you all for the comments and valuable feedback...
@Christoph,
I think, we can certainly control the behavior of module in case of retry scenarios.
By default, module will keep on overwriting the files (in case of receiver channel retries), but apparently we can include file existence check before writing files on backup FTP/NFS folder.
So, if in case there are files available on the backup folder with the same name (and if needed we can check the filesize also) then module processing can be stopped with a warning without overwriting the files. For instance, below code can be used for checking the existence of file on NFS server:
File file = new File(Path);
if(file.exists())
{
Audit.addAuditLogEntry(amk, AuditLogStatus.WARNING, "No need to overwrite file because it's already present ");
}
else
{
logic to create backup files......
......
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "File Successfully Written");
}
Thanks
Amit Srivastava
Hi Amit,
nice.
Litle question: in one of your picture, I can see you have the Module Parameter: pwd : ***** == *****.
so for parameter name "pwd", it seems PI recognizse this is for a password and so add these *** == ***. Do you know the list of parameter names (like "pwd") for which we could have somehting else than a string, or is it the only one?
regards
Mickael
Hello,
U can mask as many values as u want by giving the parameter names staring with "pwd***"
under Module Configuration like pwd_HostName, pwd_Username, pwd_FolderName etc.
Thanks
Amit Srivastava
ok Thanks Amit.
Hello Amit,
For reading the File name in receiver adapter module:
//Reading file name from message header
MessagePropertyKey mpk = new MessagePropertyKey("FileName","http://sap.com/xi/XI/System/File");
String filename = msg.getMessageProperty(mpk);
In my scenario there are no ESR objects, hence no namespace is there.
Is the piece of logic in your blog is true for my scenario also.
Is http://sap.com/xi/XI/System/File default namespace.
Regards,
Ujjwal Kumar
Hi Amit,
I am trying to compile the above code in NWDS but struggling to get supporting jar files. I have tried to download jar files from SDN but it is in ".SAR" format which I am unable to extract or use.
Could you please help me in getting the supporting jar files?
I have used below steps to download Jar files:
1. Enter service.sap.com/patches as address in your browser window.
2. Click on the left side on: Entry by Application Group
3. Navigate: Support Packages and Patches -> SAP NetWeaver -> SAP NETWEAVER -> SAP NETWEAVER 7.0/SAP NETWEAVER PI 7.1-> Entry by Component -> J2EE Adapter Engine (PI/XI)/Adapter Engine (Java EE).
4. Expand XI ADAPTER FRAMEWORK CORE 7.00/7.10 -> #OS independent and
5. download the SP level, that fits your PI installation.
6. Extract the downloaded .sca file recursively, until you get the .jar files.
Thanks,
Abhishek