Skip to Content
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? image 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. image 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, 8) + “-” + 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… | image
To report this post you need to login first.

17 Comments

You must be Logged on to comment or reply to a post.

  1. Hi Alessandro,

    Great weblog and a new idea. Just one question though. The API’s that you have used are they documented anywhere. I couldnt find the Javadocs for these API’s anywhere especially the ones in packages:

    import com.sap.aii.ibrun.sbeans.mapping.*;
    import com.sap.aii.ibrun.server.mapping.MappingHandler;
    import com.sap.aii.ibrun.server.mapping.api.TraceList;

    Just wanted to make sure that these API’s are released for client use.

    Regards
    Sidharth

    (0) 
    1. Alessandro Guarneri Post author
      Hi Sidharth,
      You’re 100% right, I should have mentioned that most of the API’s I used are not documented, and thus not released (read: SAP would label it “Use at your own risk”, just like any other SDN “invention”…)
      Nevertheless it’s really empiric: give it a try and see yourself how reliable this module is.
      Btw, it was hard job of reverse engineering.

      Take care,
      Alex

      (0) 
      1. Hi Alex,

        In our project also we had this requirement wherein we had to transform the Source Structure in XML to Target Structure in Module before the message was passed on to Integration Server. We used simple SAX parsing API’s to build the target structure.

        That didnt have the risk of using such API’s. Your thoughts are highly appreciated.

        Regards
        Sidharth

        (0) 
        1. Alessandro Guarneri Post author
          I perfectly understand.
          Your approach is the official one, always good, especially with a cautious customer, while my “little creature” here is the son of sin 🙂

          Take care,
          Alex

          (0) 
  2. Hi Alex,
           It was one of those days in xi forums, when you helped me on my first module creation, Great work, i am all set to try my hand at your weblog now.

    I appreciate your idea !!

    Regards,
    Anirban.

    (0) 
  3. Hi Alessandro.

    That’s a really great job 🙂
    I browsed the IB coding and I couldn’t find any other way to execute the mapping from the module. Even reflection is really necessary to get AbstractTrace because the getTrace() method is package local.

    I can also mention that it is the best way to process large files. Because in case of 200MB files the system needs really large buffers for JCo communication.

    Although the next release will have different interfaces you’ll need only a little change in your coding to make it working again 🙂
    And regarding XI30 these interfaces are really stable so it is almost 100% safe to use them.

    Best regards
    Dmitry

    (0) 
  4. Bart Bauwmans
    Hi Alessandro,

    I’m very impressed with this weblog, unbelievable!

    Currently I’ve made a self-mode module which I can call in the adapter. Now I want to add parameters to the module to make it more dynamic, but I do not seem to be able to fetch them in my module.

    I use one of you expressions:

    Hashtable mp = (Hashtable) inputModuleData.getSupplementalData(“module.parameters”);

    Then I want to get a certain parameter (e.g. myTest) but I does not work:

    String test = mp.get(“myTest”);

    Do you have any idea, or could you maybe elaborate more on this topic?

    Thanks in advance!!!

    Kind regards,

    Bart

    (0) 
    1. Alessandro Guarneri Post author
      Hi Bart, and thanx for your comment, really appreciated.
      Indeed the methodology you mention to get module parameters was not workin’ for me either, so wrote a small but effective private method, which I always use in AFW module dev, visible at the end of the code in this weblog too.

      private String mpget(String pname) {
      return (String) mc.getContextData(pname);
      }

      The mc object a private reference to the moduleContext parameter passed in the main method. This definitely works!

      Take care,
      Alex

      (0) 
  5. Bhavesh Kantilal
    Alex,
    More than six months since you wrote this blog , and I somehow seem to have missed it. Just found it and I am amazed.

    Cant wait to try out this approach and see how it works. Great Job..!!

    Regards,
    Bhavesh

    (0) 
  6. Hi Alex,

    This is simply great… very interesting and a great blog. Looking fwd more and more blog form you.

    Regards,
    Prakash

    (0) 
  7. Edgar Hußmann
    Hi Alessandro,

    A very great weblog.

    I tried it and it worked at once.
    But unfortunately only until the next restart of the java engine. After a restart the Adapter Module crashes while calling the mapping runtime. If some other message, which goes the “normally” way across XI, comes before the “Adapter-modul-message” everything is o.k.
    Because  – as mentioned above – we use undocumented APIs, there is no support from SAP. I only got the statement, that we have to do an initializing startup of the mapping-Runtime using the class MappingServiceImpl.
    Do you have any idea, how that works?

    Regards
    Edgar

    (0) 
    1. Alessandro Guarneri Post author
      Hi Edgar,
      Can you give just a couple of hints?
      1. who told you about MappingServiceImpl and in which package is it?
      2. can you copy-paste the Exception you got?

      Alex

      (0) 

Leave a Reply