Imagine you have to read with XI flat lines by means of a JMS adapter.
The content of the JMS message thus is a set of flat lines that you
must probably transform into suitable XML format (with the
localejbs/AF_Modules/MessageTransformBean module) just before sending
to Integration Server for routing, mapping etc.
Using the lastFieldsOptional parameter enables you to avoid problems if
the external application stops sending bytes as soon as no more data
are present. And now we go straight to the problem.
The problem<br />
In my case the legacy system is not only trimming the line as soon as no more data are present, but even the last field is trimmed. Let’s clarify.
REC1 is defined as FIELD1, FIELD2, FIELD3, FIELD4 with respective lengths 10,10,20,20
Suppose the legacy put in the queue something like this:
REC1FIELD1xxxxFIELD2xxxxFIELD3xxxxxxxxxxxxxx(EOL)
In this case FIELD4 is not present, but the lastFieldsOptional parameter saves us from a bad Java exception.
Now suppose the legacy put in the queue somthing like this:
REC1FIELD1xxxxFIELD2xxxxFIELD3xxxx(EOL)
Here we have a serious problem, because FIELD3 will not be read by the MessageTransformBean
module, and so will not be present in the resulting XML document. I
guess that’s because the field is too short (10) compared to the
declared length (20), and we cannot put the blame on it! So here comes the solution.
The solution
After pulling my hair for a while with appearently non-existing
additional undocumented parameters for the MessageTransformBean module,
I resolved that a good custom module could have be written and put in
the channel in order to extend the module chain. In this weblog I won’t
concentrate on the details concerning custom module development (take a
look at this howto), but I will just mention that I used the
great SAP NetWeaver Developer Studio, which allowed me to create EJB
project, EA project, properly set descriptor xml files, bundle
everything together and deploy to J2EE… everything in a bunch of
minutes!
If you are wondering +why I don’t calculate the total length of the
given record to be padded+ by summing the values of
xml.REC1.fieldFixedLengths, here is the answer: inside my module code,
only my module context is accessible through a matching instance of
ModuleContext class, so that I’m completely blind about other modules
configuration. Actually I’m pretty sure there’s a way to do it, but I
guess not without some hacking I don’t mean to do. 🙂
The code
package com.guarneri.xi.afw.modules.jms;
// Standard ejb imports
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.ejb.CreateException;
// XI specific imports
import com.sap.aii.af.mp.module.ModuleContext;
import com.sap.aii.af.mp.module.ModuleData;
import com.sap.aii.af.mp.module.ModuleException;
import com.sap.aii.af.ra.ms.api.*;
import com.sap.aii.af.service.auditlog.*;
// Other imports
import java.util.*;
/**
-
@ejbLocal <{com.benetton.xi.afw.modules.jms.JMSPadderLocal}>
-
@ejbLocalHome <{com.benetton.xi.afw.modules.jms.JMSPadderLocalHome}>
-
@stateless
*/
public class JMSPadderBean implements SessionBean {
private final String AUDITSTR = “guarneri.com/JMSPadder – “;
private final String MP_PREFIX = “jmspad.”;
private final String LAST_CHAR_HEX = “lastCharHex”;
private final String CARRIAGE_RETURN = “0C”;
private char crlf;
private ModuleContext mc;
public ModuleData process(ModuleContext moduleContext, ModuleData inputModuleData) throws ModuleException {
Object obj = null; // Handler to get Principle data
Message msg = null; // Handler to get Message object
Hashtable mp = null; // Module parameters
AuditMessageKey amk = null;
// Needed in order to write out on the message audit log
ModuleException mEx = null;
Date dstart = new Date();
// Creation of basic instances
try {
obj = inputModuleData.getPrincipalData();
msg = (Message) obj;
amk = new AuditMessageKey(msg.getMessageId(), AuditDirection.OUTBOUND);
mp = (Hashtable) inputModuleData.getSupplementalData(“module.parameters”);
mc = moduleContext;
} catch (Exception e) {
Audit.addAuditLogEntry(amk, AuditLogStatus.ERROR, AUDITSTR + “Error while creating basic instances (obj,msg,amk,mp)”);
throw mEx = new ModuleException(AUDITSTR + “Error while creating basic instances (obj,msg,amk,mp)”);
}
Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, AUDITSTR + “Process started”);
// Main process –
BEGIN
// What is the record (line) separator?
String lastCh = mpget(MP_PREFIX + LAST_CHAR_HEX);
if (lastCh == null)
lastCh = CARRIAGE_RETURN;
crlf = (char) Integer.parseInt(lastCh, 16);
// Collect records to be processedand relevant total lengths
Vector vctRec = new Vector();
Vector vctRecL = new Vector();
Enumeration mcKeys = mc.getContextDataKeys();
while (mcKeys.hasMoreElements()) {
String currKey = (String) mcKeys.nextElement();
if (currKey.indexOf(LAST_CHAR_HEX) == -1 && currKey.indexOf(“module.key”) == -1) {
vctRec.add(currKey.substring(currKey.indexOf(“.”) + 1, currKey.length()));
vctRecL.add(mpget(currKey));
}
}
// We have at least one record type to process
if (vctRec.size() > 0) {
byte[] baPayload = msg.getDocument().getContent();
Vector lines = new Vector();
StringBuffer line = new StringBuffer();
try {
for (int i = 0; i < baPayload.length; i++) {
char currentChar = (char) baPayload[i];
if (currentChar != crlf)
line.append(currentChar);
else {
lines.add(line.toString());
line = new StringBuffer();
}
}
} catch (Exception e1) {
throw new ModuleException(e1.toString() + e1.getMessage());
}
String strPayload = new String();
try {
// And we go for padding
for (int i = 0; i < lines.size(); i++) {
String currLine = (String) lines.get(i);
for (int j = 0; j < vctRec.size(); j++) {
if (currLine.startsWith((String) vctRec.get(j))) {
Integer RecL = new Integer((String) vctRecL.get(j));
int npad = RecL.intValue() – currLine.length();
for (int k = 0; k < npad; k++) {
currLine += ” “;
}
}
}
// Assemble back single line (padded) to full message content
strPayload += currLine + crlf;
}
} catch (Exception e2) {
throw new ModuleException(e2.toString() + e2.getMessage());
}
// New payload insertion
try {
XMLPayload newPayload = msg.getDocument();
newPayload.setContent(strPayload.getBytes());
msg.setDocument(newPayload);
inputModuleData.setPrincipalData(msg);
} catch (Exception e) {
Audit.addAuditLogEntry(amk, AuditLogStatus.ERROR, AUDITSTR + “Error while inserting new payload”);
throw mEx = new ModuleException(AUDITSTR + “Error while inserting new payload”);
}
// Return of manipulated message
Date dend = new Date();
Audit.addAuditLogEntry(
amk,
AuditLogStatus.SUCCESS,
AUDITSTR + “Process completed ” + “(execution ” + (dend.getTime() – dstart.getTime()) + ” ms)”);
}
// Main process –
END
return inputModuleData;
}
private String mpget(String pname) {
return (String) mc.getContextData(pname);
}
public void ejbRemove() {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void setSessionContext(SessionContext context) {
myContext = context;
}
private SessionContext myContext;
/**
-
Create Method.
*/
public void ejbCreate() throws CreateException {
// TODO : Implement
}
}
</textarea>
(It's more difficult to persuade an agency not to trim a message than developping a brand new adapter 😉 )
bye