IBM Support

FTP/SFTP server User Exits in SB2Bi 5.2.5

Technical Blog Post


Abstract

FTP/SFTP server User Exits in SB2Bi 5.2.5

Body

The SB2Bi 5.2.5 fixpack included user exit functionality for FTP and SFTP Servers. A user exit allows one to hook custom java code during certain pre-defined operations. Older fixpacks had few user exit points (like OnCwdCommandBeforeExecute, OnUnknownSiteSubCommand etc) for FTP server, 4 more new user exits are added in 5.2.5 for upload and download operations. But for SFTP server, user exits are added very first time through 5.2.5.

Availability of User Exit points is nothing but Java Interfaces are made available for users' custom implementations based on requirement.

New User Exits for FTP Server and corresponding Java Interfaces:

Pre File Upload- com.sterlingcommerce.woodstock.userexit.services.ftpserver.interfaces.IFtpServerUserExit_OnPutFileBeforeExecute

Allows you to execute custom java code when FTP server has received command to put a file. This allows you to read the filename and you can use this to perform validation and many more. In case your code returns 'false', server will send a negative reply to client indicating file transfer failure. Also you can specify error message to be displayed at client end with 451 response code.

Post File Upload- com.sterlingcommerce.woodstock.userexit.services.ftpserver.interfaces.IFtpServerUserExit_OnPutFileAfterExecute

Allows you to execute your code when FTP server has received file but haven't committed into underlying storage (mailbox or filesystem). This allows you to read the file data from an input stream. You can use this to perform things like dumping file to some temporary location, decline the transfer based on size, performing content validation and many more. In case your code returns 'false', server will send a negative reply to client indicating file transfer failure. Also you can specify error message to be displayed at client end with 451 response code.

Pre File Download- com.sterlingcommerce.woodstock.userexit.services.ftpserver.interfaces.IFtpServerUserExit_OnGetFileBeforeExecute

Allows you to execute your code when FTP server has received a command to get a file. This allows you to read the file data, get the size of the file etc. You can use this to perform few validations. In case your code returns 'false', server will send a negative reply to client indicating file transfer failure. Also you can specify error message to be displayed at client end with 451 response code.

Post File Download- com.sterlingcommerce.woodstock.userexit.services.ftpserver.interfaces.IFtpServerUserExit_OnGetFileAfterExecute

This allows you to execute your code after FTP server has successfully sent a file to the client as a reponse to client's get command. This is used for some reporting/auditing. Return value of your code, doesn't anyways influence the transfer of file, since your code will be executed after transfer of the file finishes.

Java Doc APIs for these interfaces are available under install/userexit/docs folder.

Note 1 - Same kind of User Exits added to SFTP server file upload and download too. You can locate the User Exit offerings for different functions under install/properties/userexit folder

Sample User Exit Implementation

Let me take one user exit scenario here and explain how to implement custom code with the help of one of these Java Interfaces and plug it into SB2Bi.

1) User Exit Scenario - Fail FTP upload when size of the file is larger than 10 KB

2) Decide what User Exit needs to be implemented? Pre Upload or Post Upload?


It has be decided based on what file attributes/parameters are needed and what is available through each Java Interface. Since Size of the file is used for this decision making and is available only in IFtpServerUserExit_OnPutFileAfterExecute interface, we should go with Post Upload user Exit.

Here are the parameters offered by IFtpServerUserExit_OnPutFileAfterExecute. As mentioned above Java Doc APIs for these interfaces are available under install/userexit/docs folder.

 

public abstract interface com.sterlingcommerce.woodstock.userexit.services.ftpserver.interfaces.IFtpServerUserExit_OnPutFileAfterExecute {

public static final java.lang.String KEY_LOG_BUFFER = "logBuffer"; // out
public static final java.lang.String KEY_ERROR_BUFFER = "errorBuffer"; // out
public static final java.lang.String KEY_INSTANCE_NAME = "instanceName";
public static final java.lang.String KEY_CLIENT_ADDRESS = "clientAddress";
public static final java.lang.String KEY_CLIENT_PORT = "clientPort";
public static final java.lang.String KEY_SERVER_ADDRESS = "serverAddress";
public static final java.lang.String KEY_SERVER_PORT = "serverPort";
public static final java.lang.String KEY_USER_ID = "userId";
public static final java.lang.String KEY_COMMAND_NAME = "command";
public static final java.lang.String KEY_FILE_NAME = "fileName";
public static final java.lang.String KEY_FILE_SIZE = "fileSize";
public static final java.lang.String KEY_PATH = "path";
public static final java.lang.String KEY_TRANSFER_TYPE = "transferType";
public static final java.lang.String KEY_INPUT_STREAM = "input";
public static final java.lang.String KEY_CONTENT_TYPE = "contentType";
public static final java.lang.String KEY_CHAR_ENCODING = "charEncoding";
public static final java.lang.String KEY_DOCUMENT_ID = "docID";
public static final java.lang.String KEY_IS_SECURE = "isSecure";

public abstract boolean onPutFileAfterExecute(java.util.Map arg0, java.util.Map arg1) throws java.lang.Exception;
}

 

3) Next is to implement your custom class to achieve this. Here is what I wrote for this scenario.

package kk.ue.ftp;

import java.util.Map;
import com.sterlingcommerce.woodstock.userexit.services.ftpserver.interfaces.IFtpServerUserExit_OnPutFileAfterExecute;

public class FTPUserExitPostPut_SizeCheck implements IFtpServerUserExit_OnPutFileAfterExecute {

private static String CLASS = "FTPUserExitPostPut_SizeCheck";
public boolean onPutFileAfterExecute(Map<String, Object> in,
Map<String, Object> out) throws Exception {

try {

System.out.println(CLASS + " IBM USER EXIT - Input arguments -> " + in);
long size = (long)in.get(IFtpServerUserExit_OnPutFileAfterExecute.KEY_FILE_SIZE);
if(size > 10240L) {

System.out.println(CLASS + " IBM USER EXIT - onPutFileAfterExecute file is larger than 10 KB. Failing FTP Upload");
String errorText = in.get(IFtpServerUserExit_OnPutFileAfterExecute.KEY_FILE_NAME) + " from " +
in.get(IFtpServerUserExit_OnPutFileAfterExecute.KEY_USER_ID) + " is larger than 10 KB; Failing Upload";
((StringBuffer)out.get(IFtpServerUserExit_OnPutFileAfterExecute.KEY_ERROR_BUFFER)).append(errorText);
((StringBuffer)out.get(IFtpServerUserExit_OnPutFileAfterExecute.KEY_LOG_BUFFER)).append(errorText);

return false;

} else {
System.out.println(CLASS + " IBM USER EXIT - success");
((StringBuffer)out.get(IFtpServerUserExit_OnPutFileAfterExecute.KEY_LOG_BUFFER)).
append(IFtpServerUserExit_OnPutFileAfterExecute.KEY_FILE_NAME). append(" is smaller than 10 KB");

return true;

}
}catch(Exception e) {
System.out.println(CLASS + " IBM USER EXIT - Ignore exception " + e);
e.printStackTrace(System.out);
}
return true;
}
}}


4) Create a jar with this class. I used eclipse and Ant tools for the same. You may find step-by-step guide here. Generated jar named "UserExitsIBMftp.jar"

5) Plug in the jar with SB2Bi

+5.1 Add custom class name from #3 into install/properties/userexit/FtpServerUserExits.xml under corresponding user exit bean tag.

<bean id="com.sterlingcommerce.woodstock.userexit.services.ftpserver.interfaces.IFtpServerUserExit_OnPutFileAfterExecute" class="com.sterlingcommerce.woodstock.userexit.services.ftpserver.FtpServerUserExit">
<property name="implementations">
<list>
<value>kk.ue.ftp.FTPUserExitPostPut_SizeCheck</value>
</list>
</property>
<property name="generalParameters">
<props>
<prop key="return.on.exception">false</prop>
<prop key="pool.size">5</prop>
<prop key="maximum.queue.length">5</prop>
<prop key="wait.time">10</prop>
<prop key="execution.threshold.time">600000</prop>
</props>
</property>
</bean>

 

There are few other properties associated with each of the beans. You can leave them default for basic understanding of user exit. Refer to FTP Server Doc here

Note 2 - You can have multiple class implementations for a particular interface and each of them must be entered in this xml file in the same order as you want them to be executed in that chain. In case of multiple classes, outcome of operation is dependent on outcome of each of these classes. (class1 && class2 && class2)

e.q., <list>
<value>classname1</value>
<value>classname2</value>
<value>classname3</value>

</list>


Note 3 - Changes to this xml won't survive with upgrade/fixpack installation. They need to be re-configured.

+5.2 Plug in jar file from #4 to SB2Bi using install3rdParty.sh script. This adds VENDOR_JAR entry to properties/dynamiclcasspath.cfg and properties/dynamiclcasspathAC.cfg (for containers)

e.g., ./install3rdParty.sh userexitFTP 1_0 -j ../../UserExitsIBMftp.jar

6) start SB2Bi to take all these changes effective.

Note 4 - If you want to remove any implementation from chain then just edit the FtpServerUserExits.xml and restart the FTP Server instance only. Doesn't require restart of application. You can even add new implementation to the chain provided that they were already present in jar (from #5.2) at the time application was started.


Lastly, TESTING & TROUBLESHOOTING

First file upload shows successful. Second one correspond to failure due to user exit criteria (file is larger than 10 K). Similar errors also can be tracked through Communication Session in dashboard.

image

 

image



noapp.log shows lines correspond to System.out in custom class for troubleshooting. Similarly standard application specific ftp errors in ftp.log.


image
 

FAQ

1) Can I enable my custom user exit implementation for one FTP Server Adapter instance and disable for another?

Ans. Default User Exit functionality is global. i.e., Your custom user exit code is applied to all FTP Server Adapter instances.

However, You can have logic in your custom user exit class based on current adapter instance available through KEY_INSTANCE_NAME parameter from input map.

public static final java.lang.String KEY_INSTANCE_NAME = "instanceName";
 

[{"Business Unit":{"code":"BU059","label":"IBM Software w\/o TPS"},"Product":{"code":"SS3JSW","label":"IBM Sterling B2B Integrator"},"Component":"","Platform":[{"code":"PF025","label":"Platform Independent"}],"Version":"","Edition":"","Line of Business":{"code":"LOB59","label":"Sustainability Software"}}]

UID

ibm11121127