Authors: Vince Tkac, B2B Architect, IBM and Philip Ross, Software Engineer, IBM
As promised, this is a follow-on article in our user exit series.
Just as a reminder:
A user exit is a way for customers/partners to link in custom code during certain pre-defined operations.
The new user exits in 5.2.4.2 are hooked into mailbox message add, delete and extract.
These user exits provide the ability to audit, validate file names, examine file content or refuse a message/transfer when messages are added, deleted or extracted from a mailbox.
This article will show how to use the mailbox message add user exit to accomplish white list and black list of file extensions. This concept can be extended to look at file name, upload time and even file contents.
To accomplish this, create a class that implements com.sterlingcommerce.woodstock.userexit.services.mailbox.interfaces.IMailboxUserExit_OnMessageAdd. Let’s start by just having the user exit print a message so we can see it is being called.
package com.sterlingcommerce.woodstock.mailbox.userexit;
import java.util.List;
import java.util.Map;
import com.sterlingcommerce.woodstock.userexit.services.mailbox.interfaces.IMailboxUserExit_OnMessageAdd;
public class MailboxSyncUserExit implements IMailboxUserExit_OnMessageAdd {
public boolean onMessageAdd(Map<String, Object> arg0, Map<String, Object> arg1) throws Exception {
String name = (String) arg0.get(IMailboxUserExit_OnMessageAdd.KEY_MESSAGE_NAME);
System.out.println("MyUserExit: received " + name);
return true; // Go ahead with add.
}
}
Once we have the class written, compile it, add it to a jar and put that jar in the dynamicclasspath.cfg file so the system will find it.
Create a mailbox user exit xml file to tell the system about the user exit. The files goes in install/properties/userexit and should be named MailboxUserExits.xml.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="MailboxUserExitStore" class="com.sterlingcommerce.woodstock.userexit.services.mailbox.MailboxUserExitStore">
<property name="userExits">
<map>
<entry key="com.sterlingcommerce.woodstock.userexit.services.mailbox.interfaces.IMailboxUserExit_OnMessageAdd">
<ref bean="com.sterlingcommerce.woodstock.userexit.services.mailbox.interfaces.IMailboxUserExit_OnMessageAdd"/>
</entry>
</map>
</property>
</bean>
<bean id="com.sterlingcommerce.woodstock.userexit.services.mailbox.interfaces.IMailboxUserExit_OnMessageAdd" class="com.sterlingcommerce.woodstock.userexit.services.mailbox.MailboxUserExit">
<property name="implementations">
<list>
<value>com.sterlingcommerce.woodstock.mailbox.userexit.MailboxSyncUserExit</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">1000</prop>
<prop key="wait.time">10</prop>
<prop key="execution.threshold.time">600000</prop>
</props>
</property>
</bean>
</beans>
Start the system up and ftp a file into a mailbox. You should immediately see the message from System.out.println in noapp.log.* in the install/logs directory.
Now that we have verified the user exit is being called, we will create a properties file to store information for our black/white lists. It should look like:
mailbox_path.1=/producer
allow_only.1=.*\\.dat
mailbox_path.2=/producer2
disallow_all.2=.*\\.exe,.*\\.com,.*\\.dll
We will name this file userexitlisting.properties
Next, add this new properties file to servers.properties
The modified user exit looks like:
package com.sterlingcommerce.woodstock.mailbox.userexit;
import java.util.*;
import com.sterlingcommerce.woodstock.userexit.services.mailbox.interfaces.IMailboxUserExit_OnMessageAdd;
import com.sterlingcommerce.woodstock.workflow.InitialWorkFlowContext;
import com.sterlingcommerce.woodstock.workflow.InitialWorkFlowContextException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import com.sterlingcommerce.woodstock.util.frame.Manager;
public class MailboxSyncUserExit implements IMailboxUserExit_OnMessageAdd {
private final static String MAILBOX_PATH_1 = "mailbox_path.1";
private final static String MAILBOX_PATH_2 = "mailbox_path.2";
private final static String ALLOW_ONLY_1 = "allow_only.1";
private final static String DISALLOW_ALL_2 = "disallow_all.2";
static { Manager.doStaticInit(); }
public boolean onMessageAdd(Map<String, Object> arg0, Map<String, Object> arg1) throws Exception {
try {
return onMessageAddWrapped(arg0, arg1);
} catch (Exception e) { e.printStackTrace(); }
return(false);
}
private boolean onMessageAddWrapped(Map<String, Object> arg0, Map<String, Object> arg1) throws Exception {
String name = (String) arg0.get(IMailboxUserExit_OnMessageAdd.KEY_MESSAGE_NAME);
Long messageId = (Long)loadPropObj("messageId", arg0, true);
String mailboxPath = loadProp("mailboxPath", arg0, true);
String userId = loadProp("userId", arg0, true);
String messageName = loadProp("messageName", arg0, true);
String documentId = loadProp("documentId", arg0, true);
Boolean add = true;
Properties props = Manager.getProperties("userexitlisting");
if(!mailboxPath.equals("/producer") && !mailboxPath.equals("/producer2")) return add;
if(mailboxPath.equals(props.get(MAILBOX_PATH_1))) {
List<String> allow_list = Arrays.asList(((String)props.get(ALLOW_ONLY_1)).split(","));
add = false;
Iterator itr = allow_list.iterator();
while(itr.hasNext() ) {
if(messageName.matches((String)itr.next())) add = true;
}
} else {
List<String> disallow_list = Arrays.asList(((String)props.get(DISALLOW_ALL_2)).split(","));
Iterator itr = disallow_list.iterator();
while(itr.hasNext()) {
if(messageName.matches((String)itr.next())) add = false;
}
}
System.out.println("MyUserExit: received " + name);
return add; // Go ahead with add.
}
private String loadProp(String name, Map<String, Object> inargs, boolean required) {
String str = (String) inargs.get(name);
if ( (str == null) || str.isEmpty() ) {
if (required) { throw new IllegalArgumentException("Expected parameter not specified " + name); }
}
return(str);
}
private Object loadPropObj(String name, Map<String, Object> inargs, boolean required) {
Object obj = inargs.get(name);
if ( (obj == null) ) {
if (required) { throw new IllegalArgumentException("Expected parameter not specified " + name); }
}
return(obj);
}
}
The user exit will now either allow or disallow files to be added to specified mailboxes based on the file extensions included in the properties file.
The user exit can be further modified to check other file attributes or content but remember that the user exit runs on the protocol adapter thread so if you do time intensive work here, it will block the protocol adapter from handling new connections.
userexitlisting.zip
Update: We recently found an issue at a customer site where the user exit framework was returning an error under high load. This was a result of this setting in the xml config. I have modified the example above to make this 10000 instead of 5.
<prop key="maximum.queue.length">5</prop>
The error received looked like:
<Errors>
<Error ErrorCode="YCP0195_55SP2"
ErrorDescription="Error description not available" ErrorRelatedMoreInfo="">
<Attribute Name="ErrorCode" Value="YCP0195_55SP2"/>
<Attribute Name="ErrorDescription" Value="Error description not available"/>
<Stack>com.yantra.yfc.util.YLockException
at com.yantra.ycp.core.lock.NamedObject.getLock(NamedObject.java:64)
at com.yantra.ycp.core.lock.YCPLockManager.getLockable(YCPLockManager.java:45)
at com.yantra.ycp.core.ue.YCPUEDefaultImpl.handleLimitedInvocation(YCPUEDefaultImpl.java:281)
at com.sterlingcommerce.woodstock.userexit.proxy.MailboxUserExitProxy.onMessageAdd(MailboxUserExitProxy.java:102)
at com.sterlingcommerce.woodstock.userexit.services.mailbox.invokers.MailboxUserExitOnMessageAddInvoker.userMethod(MailboxUserExitOnMessageAddInvoker.java:79)
at com.sterlingcommerce.woodstock.userexit.services.mailbox.invokers.MailboxUserExitOnMessageAddInvoker.userMethod(MailboxUserExitOnMessageAddInvoker.java:20)
at com.sterlingcommerce.woodstock.userexit.UserExitInvoker.call(UserExitInvoker.java:46)
at com.sterlingcommerce.woodstock.userexit.services.mailbox.MailboxUserExitHandler.OnMessageAdd(MailboxUserExitHandler.java:207)
at com.sterlingcommerce.woodstock.mailbox.impl.repositoryImpl.RepositoryDB$1.body(RepositoryDB.java:333)
at com.sterlingcommerce.woodstock.mailbox.db.DatabaseOperation._runInTransaction(DatabaseOperation.java:335)
at com.sterlingcommerce.woodstock.mailbox.db.DatabaseOperation.runInTransaction(DatabaseOperation.java:144)
at com.sterlingcommerce.woodstock.mailbox.impl.repositoryImpl.RepositoryDB.addMessage(RepositoryDB.java:309)
at com.sterlingcommerce.woodstock.mailbox.impl.repositoryImpl.RepositoryImpl.lowLevelAdd(RepositoryImpl.java:382)