Additional Blog Posts by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
Read only
Former Member
1,887 Views
17 Comments
h3. Introduction I have recently been searching for a module that could execute a generic mapping program, but I had no satisfaction. If you are wondering why I needed it, keep up reading and you'll probably find out yourself many scenarios in which this technique can be applied. I searched standard doc first, only finding the MessageTransformBean (http://help.sap.com/saphelp_nw04/helpdata/en/57/0b2c4142aef623e10000000a155106/content.htm) which has serious limitations: first of all it was born for XI 2.0, and it needs the mapping program to be packaged and deployed along with the module in a .sda file. Sure, a module that can execute a Java or XSLT mapping program packaged in the module .jar itself could have been enough, but I just never stop, I'm not satisfied by half-solutions... h3. The Goal So the goal was to have my adapter module perform these basic tasks: ** read its own parameters in the Communication Channel configuration to get mapping coordinates and other settings ** ** dynamically retrieve the mapping (be that graphical, Java or XSLT) ** ** execute the mapping by invoking the mapping runtime ** ** put the resulting document as message payload ** h3. Graphically Speaking In a common and simplified XI scenario, data flow can be represented by the picture below. Notice that steps 3 and 4, in which data are sent via RFC by the Integration Engine to the Mapping Runtime and the other way around (synchronous call to a Jco Server instance), is exactly what I aimed to avoid. Performance is seriously affected by this step. More, suppose you have an ABAP mapping as the main transformation program in your scenario, but you need a kind of pre-mapping Java or XSLT program to be executed before the ABAP class can process data... Don't you think it's really an overhead to go back and forth between the two stacks? So comes the modified scenario, in which steps 3 and 4 (which is actually a single logical step, that is one mapping) are removed. In the picture below the Mapping Runtime is now invoked by the Adapter Framework in step 2 before the message is sent to the ABAP stack, and in step 5 before the message is sent the Messaging System for final delivery. In this kind of scenario, the two steps can be both present, or only one of them: one step represents one logical step, that is one transformation. The module is flexible enough to be used both in sender and receiver communication channels: if you use it in step 2, say it acts like a kind of pre-processor, while if you use it in step 5, it acts like a post-processor. You can even have multiple instances of the same module in one communication channel, so that you can emulate a multi-mapping, where the result document of previous mapping will be the source of the following one... h3. NWDS project So let's get it built. For general information about how to build the right project in NetWeaver Developer Studio, please refer to {code:html}this SAP howto{code}. Here I will just underline and focus on needed additional settings. Very important now are *J2EE references*, that you can find in the application-j2ee-engine.xml of your Enterprise Application addition project. In addition to common references needed by a module tu run smoothly in the J2EE, you need a couple more. You can find here below the xml source needed. +*Hint*+. Remember to set up also the *JNDI name* in your ejb-j2ee-engine.xml, which must match the name you'll use to invoke the module from a communication channel. Now you're almost there... Just missing one thing more: the bean code. I won't comment the code here in this weblog: it's really full of comments in itself. package com.guarneri.xi.afw.modules; 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.*; // XML manipulation imports import com.sap.aii.ibrun.sbeans.mapping.*; import com.sap.aii.ibrun.server.mapping.MappingHandler; import com.sap.aii.ibrun.server.mapping.api.TraceList; import com.sap.guid.*; // Other imports import java.lang.reflect.Field; import java.util.Date; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; /** * @ejbHome <{com.guarneri.xi.afw.modules.AFWJavaMapHome}> * @ejbLocal <{com.guarneri.xi.afw.modules.AFWJavaMapLocal}> * @ejbLocalHome <{com.guarneri.xi.afw.modules.AFWJavaMapLocalHome}> * @ejbRemote <{com.guarneri.xi.afw.modules.AFWJavaMap}> * @stateless * @transactionType Container */ public class AFWJavaMapBean implements SessionBean { final String auditStr = "*** AFWJavaMap - "; ModuleContext mc; boolean hangOnError; 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 public ModuleData process( ModuleContext moduleContext, ModuleData inputModuleData) throws ModuleException { Date dstart = new Date(); byte[] out = null; // Mapping result // Creation of basic instances try { mc = moduleContext; obj = inputModuleData.getPrincipalData(); msg = (Message) obj; mp = (Hashtable) inputModuleData.getSupplementalData( "module.parameters"); if (msg.getMessageDirection() == MessageDirection.INBOUND) amk = new AuditMessageKey( msg.getMessageId(), AuditDirection.INBOUND); else amk = new AuditMessageKey( msg.getMessageId(), AuditDirection.OUTBOUND); } catch (Exception e) { Audit.addAuditLogEntry( amk, AuditLogStatus.ERROR, auditStr + "Error while creating basic instances (obj,msg,amk,mp)"); throw new ModuleException( auditStr + "Error while creating basic instances (obj,msg,amk,mp)"); } Audit.addAuditLogEntry( amk, AuditLogStatus.SUCCESS, auditStr + "Process started"); // See what we want to do in case of mapping failure (default is HANG) hangOnError = true; String hoe = mpget("hang.on.error"); if (hoe != null) hangOnError = !hoe.equalsIgnoreCase("no") || !hoe.equalsIgnoreCase("false"); // Audit source document if requested String auditSource = mpget("audit.source.message"); if (auditSource != null) Audit.addAuditLogEntry( amk, AuditLogStatus.SUCCESS, auditStr + "Source document: " + new String(msg.getDocument().getContent())); // SWCV guid generation String swvcGuidStr = mpget("swcv.guid"); if ((swvcGuidStr == null) || (swvcGuidStr.length() != 32 && swvcGuidStr.length() != 36)) return this.handleReturn( inputModuleData, auditStr + "No SWVC Guid was provided or Guid has wrong length. Exiting!", true); if (swvcGuidStr.length() == 32) // Perform some required (homemade) formatting swvcGuidStr = swvcGuidStr.substring(0, 😎 + "-" + swvcGuidStr.substring(8, 12) + "-" + swvcGuidStr.substring(12, 16) + "-" + swvcGuidStr.substring(16, 20) + "-" + swvcGuidStr.substring(20, 32); IGUID swcv = null; try { IGUIDGeneratorFactory guidGenFac = GUIDGeneratorFactory.getInstance(); IGUIDGenerator guidGen = guidGenFac.createGUIDGenerator(); swcv = guidGen.parseGUID(swvcGuidStr); } catch (GUIDFormatException e2) { return this.handleReturn( inputModuleData, auditStr + "Error while creting SWVC Guid (GUIDFormatException). Exiting!", true); } // Get the mapping type (if none provided assume it's graphical mapping) String mappingType = mpget("mapping.type").toUpperCase(); if (!mappingType.equalsIgnoreCase("GRAPHICAL") && !mappingType.equalsIgnoreCase("JAVA") && !mappingType.equalsIgnoreCase("XSLT")) { return this.handleReturn( inputModuleData, auditStr + "Wrong mapping type supplied " + "(only GRAPHICAL | JAVA | XSLT are allowed). Exiting!",true); } else if (mappingType == null) mappingType = "GRAPHICAL"; // Get mapping namespace (this is not vital, as the mapping search // is tolerant enough to look into the whole SWVC) String ns = mpget("namespace"); if (ns == null) ns = new String(); // Get mapping name. Here I wanna be a good boy and allow people // to put it the natural way 😉 String mappingName = mpget("mapping.name"); if (mappingName == null) return this.handleReturn( inputModuleData, auditStr + "No mapping name was provided. Exiting!", true); if (mappingType.equalsIgnoreCase("GRAPHICAL")) { // Let's build the real class name mappingName = "com/sap/xi/tf/" + "_" + mappingName + "_"; mappingType = "JAVA"; } // Check which trace level was requested (default to warning) char trLevCh = '1'; String trLevel = mpget("trace.level"); if (trLevel != null) { if (trLevel.equalsIgnoreCase("INFO")) trLevCh = '2'; else if (trLevel.equalsIgnoreCase("DEBUG")) trLevCh = '3'; else if (trLevel.equalsIgnoreCase("WARNING")) trLevCh = '1'; else if (trLevel.equalsIgnoreCase("OFF")) trLevCh = '0'; } // Create the messenger object Messenger mes = MappingDataAccess.createMessenger(trLevCh); // Instantiate MappingData object MappingData md = null; try { md = MappingDataAccess.createMappingData( mappingType, mappingName, ns, swcv, -1, "AFWJavaMap"); } catch (Exception e) { return this.handleReturn( inputModuleData, auditStr + "Error instantiating MappingData: " + e); } // Put this MappingData object in an array (this emulates an // interface mapping with several mapping steps) MappingData[] mds = new MappingData[1]; mds[0] = md; // Build the map object Map map = null; try { // Here I just feed the messageId but with same tecnique // other useful stuff can be put // (it strongly depends on which Header Fields you access // in your mapping!) map = new HashMap(); map.put("MessageId", msg.getMessageId()); // Here I need to use reflection to get a valid instance // of the trace object Object tr = null; Class c = mes.getClass(); Field trf = c.getDeclaredField("trace"); trf.setAccessible(true); tr = trf.get(mes); map.put("MappingTrace", tr); } catch (Exception e) { return this.handleReturn( inputModuleData, auditStr + "Error during HashMap and MappingTrace creation - " + e + " - Exiting!"); } // Create the MappingHandler object MappingHandler mh = null; try { mh = new MappingHandler( msg.getDocument().getContent(), mds, map, mes); } catch (Exception e) { return this.handleReturn( inputModuleData, auditStr + "Error during MappingHandler creation - " + e + " - Exiting!"); } // Rock n' Roll: execute the mapping boolean mappingFailed = false; try { out = mh.run(); } catch (Exception e1) { mappingFailed = true; // handleReturn is postponed to allow mapping trace // to be in the msg audit } // Trace management TraceList tl = mes.getTraceList(); for (int i = 0; i ") .getBytes()); lmsg.setDocument(dummypl); } catch (PayloadFormatException e) { } catch (InvalidParamException e) { } } return md; } private ModuleData handleReturn( ModuleData md, String strmsg, boolean throwEx) throws ModuleException { throw new ModuleException(strmsg); } 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 { } } h3. Usage The module supports these 8 parameters: | Parameter Name | Mandatory | Values | Description | | swcv.guid | Yes | Any valid SWCV Guid | The guid of the Software Component Version in which the mapping resides | | mapping.name | Yes | Any valid mapping name | The name of the mapping program. In case of graphical mapping it's just the name of the mapping you can see in the repository. In case of Java mapping, the name must be fully qualified with package (e.g. com.yourcompany.mapping), visible in the relevant imported archive. In case of XSLT mapping, the name of the XSL file, visible in the relevant imported archive. | | mapping.type | No | GRAPHICAL (default) | JAVA | XSLT | Determine the type of mapping | | namespace | No | Any valid namespace | The namespace in which the mapping resides. I know it sounds strange, but this is not mandatory as the mapping lookup function is just so tolerant that searches in the whole SWCV. Of course, giving the right namespace increases performance. | | trace.level | No | OFF | WARNING (default) | INFO | DEBUG | Determine the mapping trace level that you want to output in the message audit log. | | hang.on.error | No | Yes (default) | No | Determine whether the message will stop if something fails. This needs additional work: currently, upon error, a dummy XML document is put in the message in place of the real one. | | audit.source.message | No | Any value | Deactivated by default. If set, the source document will be written to the audit log. Useful for sender channels. | | audit.result.message | No | Any value | Deactivated by default. If set, the result document will be written to the audit log. Useful for receiver channels. | I think the above table is eloquent enough to let you guess how powerful this guy is... |
17 Comments