Many SCN blogs are already out there (like this excellent one:
DynamicAttributeChangeBean - The no-mapping solution to changing Dynamic Configuration ... dynamical...) explaining ways to set dynamic configuration attributes.
This blog adds another simple idea to enhance the flexibility of the bean.
- Take key and value as one set (rather than requiring key.x, value.x pattern)
- Iterate through all the module keys (skipping the top level "module.key") and obtain their values
- More importantly, provide ways to call a specific method within the module
I have created 3 methods within the bean and by suffixing the parameter name with keywords .HELP, .XPATH or .CONSTANT, I am able to call the corresponding specific method. Either new attributes are created or the existing ones get updated.
Parameter name ending with .HELP and value true shows the Help Text for this module. This can be used whenever the module usage is in question and the developer needs some help. (I sincerely wish SAP takes this idea and implements this in all their standard modules to display help, and saves a lot of googling (or binging) time of developers trying to find the available parameters/usage of a given module.)
Parameter name ending with .XPATH evaluates the given XPATH parameter value against the XML payload and dynamic attribute value. (I usually use
http://www.freeformatter.com/xpath-tester.html to validate my XPATH expression on the payload before setting it as the parameter value.)
The parameter name can be standard FileName or Directory (prefixed with the standard namespace), or an entirely new one (prefixed with any namespace) depending on the needs.
The below screenshots show an example of setting up the target directory and file name values on a receiver file channel.
Parameter name ending with .CONSTANT will simply set the constant parameter value.
I enabled Add Counter as well
Please note that this blog is not to show how to set the file/directory names. It rather is to show the ability to branch out from the standard process method to call another method to set the value. If you get the idea, you know that your custom module's functionality can be extended as much as any Java program can be - Append, Replace with a value of another existing attribute (like the @ notation used by SEEBURGER), Timestamp etc..
While this gives a choice for the developers to create as many methods as they want, simply select a specific method and pass the expected arguments to that method, it also presents the challenge of balancing the act carefully. Customizing too much and later updating even a small part of it will force testing of all interfaces that the custom module is implemented for.
I used NWDS 7.3 SP16 EHP1 for development and PO 7.4 SP11 for testing. (If you would like to know how to create a custom module, refer to this nicely written detailed blog:
How to create SAP PI adapter modules in EJB 3.0)
Here is my code.
(If you deploy this code, you will need to use "ZDynamicConfigurationBean" as the Module Name instead of "SetDynamicConfiguration" that I used in the screenshot above.
DISCLAIMER:
I used/tested this only in my temporarily built sandbox environment. Please assess the use and suitability of this code for your environment; and test it thoroughly before using it for real time purposes.
)
package com.geni.custommodule;
/*
* Author: Badari
* When: March 2016
* DISCLAIMER: I used/tested this only in my temporarily built sandbox environment. Please assess the use and suitability of this code for your environment; and test it thoroughly before using it for real time purposes.
*/
import java.io.IOException;
import java.util.Iterator;
import javax.annotation.PostConstruct;
import javax.ejb.Stateless;
import javax.ejb.Local;
import javax.ejb.LocalHome;
import javax.ejb.Remote;
import javax.ejb.RemoteHome;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.InputSource;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import javax.xml.xpath.*;
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.InvalidParamException;
/**
* Session Bean implementation class ZDynamicConfiguration
*/
@Stateless(name = "ZDynamicConfigurationBean")
@Local(value = { ModuleLocal.class })
@Remote(value = { ModuleRemote.class })
@LocalHome(value = ModuleLocalHome.class)
@RemoteHome(value = ModuleHome.class)
public class ZDynamicConfiguration implements Module {
/**
* Default constructor.
*/
AuditAccess audit;
String newLine = "\n";
String moduleHelpText = " Usage: " + newLine
+ " ZDynamicConfiguration " + newLine
+ " Parameter Name ending with .HELP, value TRUE will show this module usage help. " + newLine
+ " Parameters Names ending with .CONSTANT (eg. http://sap.com/xi/XI/System/File/filename.CONSTANT) takes the parameter value (eg INF0001) and set the dyn config (filename/INF0001). " + newLine
+ " Parameters Names ending with .XPATH (eg. http://sap.com/xi/XI/System/File/filename.XPATH) takes the parameter value as XPATH and evaluates it against the XML payload. " + newLine
+ " Parameters Names that do not end with .HELP or .XPATH or .CONSTANT are ignored.";
String defaultNamespace = "http://dynamicvariableconfiguration.com/default";
String thisProgramName = "ZDynamicConfiguration";
String XPATHValueNotFound = "XPATHValueNotFound";
MessagePropertyKey messagePropertyKey;
String moduleKeyParameterName;
String moduleKeyParameterNamespace;
String moduleKeyParameterValue;
Document doc = null;
XPathFactory factory;
XPath xpath;
public ZDynamicConfiguration() {
// TODO Auto-generated constructor stub
}
@PostConstruct
public void initializeResources() {
try {
audit = PublicAPIAccessFactory.getPublicAPIAccess()
.getAuditAccess();
} catch (Exception e) {
throw new RuntimeException(
"Cannot initialize Audit Access. Exiting the "
+ thisProgramName + ". Exception : "
+ e.getMessage());
}
}
@SuppressWarnings("unchecked")
@Override
public ModuleData process(ModuleContext moduleContext,
ModuleData inputModuleData) throws ModuleException {
Message message = (Message) inputModuleData.getPrincipalData();
MessageKey messageKey = message.getMessageKey();
// get the message xml payload and pass it to DOM. Also setup the xpath
XMLPayload xmlPayload = message.getDocument();
audit.addAuditLogEntry(messageKey, AuditLogStatus.SUCCESS, "Entered "
+ thisProgramName + " Bean...");
// log xml payload
// audit.addAuditLogEntry(messageKey, AuditLogStatus.SUCCESS,
// "Showing XML Payload: " + xmlPayload.getText());
InputSource is = new InputSource(xmlPayload.getInputStream());
DocumentBuilderFactory domFactory = DocumentBuilderFactory
.newInstance();
domFactory.setNamespaceAware(true);
DocumentBuilder builder = null;
try {
builder = domFactory.newDocumentBuilder();
doc = builder.parse(is);
} catch (ParserConfigurationException e1) {
e1.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
factory = XPathFactory.newInstance();
xpath = factory.newXPath();
// read the module parameters passed by the user (channel)
Iterator<String> parameters = (Iterator<String>) moduleContext
.getContextDataKeys();
audit.addAuditLogEntry(messageKey, AuditLogStatus.SUCCESS,
"Reading Parameter Name-Value Pairs..");
while (parameters.hasNext()) {
// Read module parameters and their corresponding values, remove
// unwanted inadvertent spaces
moduleKeyParameterName = (String) parameters.next();
moduleKeyParameterName = moduleKeyParameterName.trim();
// read module parameter keys except the default "module.key"
if (moduleKeyParameterName.equalsIgnoreCase("module.key")) {
audit.addAuditLogEntry(messageKey, AuditLogStatus.SUCCESS,
"skipping module.key");
continue;
}
// if .HELP or .XPATH or .CONSTANT is not specified, this program
// doesn't
// know what to do. Will ignore the parameter and process the next
// one
if (!moduleKeyParameterName.contains(".")) {
audit
.addAuditLogEntry(
messageKey,
AuditLogStatus.WARNING,
".HELP or .XPATH or .CONSTANT not specified in the parameter name. Don't know what to do. Ignoring and moving to the next one...");
continue;
}
moduleKeyParameterValue = moduleContext
.getContextData(moduleKeyParameterName);
audit.addAuditLogEntry(messageKey, AuditLogStatus.SUCCESS,
"moduleKeyParameterValue is : " + moduleKeyParameterValue);
if (moduleKeyParameterValue.length() > 0) {
// check if the parameter is asking to show usage help
if (moduleKeyParameterName.toUpperCase().endsWith("HELP")
&& moduleKeyParameterValue.equalsIgnoreCase("TRUE")) {
audit.addAuditLogEntry(messageKey, AuditLogStatus.SUCCESS,
"Showing module usage help : " + moduleHelpText);
}
// check if the parameter is asking for XPATH evaluation
if (moduleKeyParameterName.toUpperCase().endsWith(".XPATH")) {
splitModuleKeyParameterName(messageKey,
moduleKeyParameterName);
setDynamicWithXPATH(message, messageKey,
moduleKeyParameterNamespace,
moduleKeyParameterName, moduleKeyParameterValue);
}
// check if the parameter is asking for setting up a plain
// Constant value
if (moduleKeyParameterName.toUpperCase().endsWith(".CONSTANT")) {
splitModuleKeyParameterName(messageKey,
moduleKeyParameterName);
setDynamicWithConstant(message, messageKey,
moduleKeyParameterNamespace,
moduleKeyParameterName, moduleKeyParameterValue);
}
} else {
audit.addAuditLogEntry(messageKey, AuditLogStatus.WARNING,
"Ignoring Blank Value for Parameter : "
+ moduleKeyParameterName);
}
}
audit
.addAuditLogEntry(
messageKey,
AuditLogStatus.SUCCESS,
"Exiting "
+ thisProgramName
+ " after setting up custom dynamic configuration Name-Value Pairs");
return inputModuleData;
}
public void setDynamicWithXPATH(Message message, MessageKey messageKey,
String moduleKeyParameterNamespace, String moduleKeyParameterName,
String moduleKeyParameterValue) {
audit.addAuditLogEntry(messageKey, AuditLogStatus.SUCCESS,
"Entered setDynamicWithXPATH method..");
/*
*
* evaluate XPATH. if no value found, set the value to the default value
* to XPATHValueNotFound
*/
XPathExpression expr;
try {
expr = xpath.compile(moduleKeyParameterValue);
String result = (String) expr.evaluate(doc, XPathConstants.STRING);
/*
* if result is yielded, then set the dynamic configuration
* key/value pair todo - ensure only one value (not a node set) is
* returned as result
*/
if (result.length() > 0) {
audit.addAuditLogEntry(messageKey, AuditLogStatus.SUCCESS,
"XPATH evaluation yielded: " + result);
messagePropertyKey = new MessagePropertyKey(
moduleKeyParameterName, moduleKeyParameterNamespace);
message.setMessageProperty(messagePropertyKey, result);
audit.addAuditLogEntry(messageKey, AuditLogStatus.SUCCESS,
"Setting Dynamic Key-Value : " + moduleKeyParameterName
+ " - " + moduleKeyParameterValue + " in "
+ moduleKeyParameterNamespace);
} else {
audit
.addAuditLogEntry(
messageKey,
AuditLogStatus.WARNING,
moduleKeyParameterValue
+ " did not yield a value. Setting Dynamic Key-Value : "
+ moduleKeyParameterName + " - "
+ XPATHValueNotFound);
}
} catch (XPathExpressionException e) {
e.printStackTrace();
} catch (InvalidParamException e) {
e.printStackTrace();
}
}
public void setDynamicWithConstant(Message message, MessageKey messageKey,
String moduleKeyParameterNamespace, String moduleKeyParameterName,
String moduleKeyParameterValue) {
audit.addAuditLogEntry(messageKey, AuditLogStatus.SUCCESS,
"Entered setDynamicWithConstant method..");
try {
MessagePropertyKey messagePropertyKey = new MessagePropertyKey(
moduleKeyParameterName, moduleKeyParameterNamespace);
message.setMessageProperty(messagePropertyKey,
moduleKeyParameterValue);
audit.addAuditLogEntry(messageKey, AuditLogStatus.SUCCESS,
"Setting Dynamic Key-Value : " + moduleKeyParameterName
+ " - " + moduleKeyParameterValue + " in "
+ moduleKeyParameterNamespace);
} catch (InvalidParamException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void splitModuleKeyParameterName(MessageKey messageKey,
String moduleKeyParameterNameOriginal) {
/*
* split moduleKeyParameterName into namespace and parameter name
* Example User Input for Key:
* "http://sap.com/xi/XI/System/File/FileName.XPATH"
* namespace = "http://sap.com/xi/XI/System/File" parameter name = "filename"
*/
// if user forgets to provide the namespace, set it to default namespace
if (moduleKeyParameterNameOriginal.contains("/")) {
moduleKeyParameterNamespace = moduleKeyParameterNameOriginal
.substring(0, moduleKeyParameterNameOriginal
.lastIndexOf("/"));
} else {
audit.addAuditLogEntry(messageKey, AuditLogStatus.WARNING,
"namespace pattern not found in ParameterName: "
+ moduleKeyParameterNameOriginal
+ ".Setting default value to " + defaultNamespace);
moduleKeyParameterNamespace = defaultNamespace;
}
moduleKeyParameterName = moduleKeyParameterNameOriginal.substring(
moduleKeyParameterNameOriginal.lastIndexOf("/") + 1,
moduleKeyParameterNameOriginal.lastIndexOf("."));
audit.addAuditLogEntry(messageKey, AuditLogStatus.SUCCESS,
"moduleKeyParameterName is : " + moduleKeyParameterName);
audit.addAuditLogEntry(messageKey, AuditLogStatus.SUCCESS,
"moduleKeyParameterNamespace is : "
+ moduleKeyParameterNamespace);
}
}