Skip to Content

Today I’d like to share with you one of my favourite tricks, that is how to handle adapter modules in SOAP Sender Channels. As you may know, this is not officially supported by SAP, and you’ll notice there are some limitations as long as you experiment with my code here below, but it usually works pretty good.

I won’t waste any time in long and useless descriptions here, as the real heart of the thing is the code itself, while you can a find a whole bunch of doc, blogs and wikis on the modules matter itself. Just be aware that the code below is for PI 7.1x (though it can be easily backported to 3.0 /  7.0) and that you can safely take it as the main skeleton for any module implementation (SOAP sender as well, yes).

I have devolped this tiny thing using in a DC with NWDI and I am pretty sure you need to add a  a few more dependency to exploit the classes that are needed to handle the SOAP Sender romance, but now I honestly don’t remember so I’ll just put a couple of screenshot of my Java Build Path libs and Used DCs, from which it should be pretty easy to guess what’s missing in your project compared to a common PI Adapter Module one. 

Enjoy.

Libs 

DCs 

 

 

  

   

  

  

   

  

    /**
 * 
 */
package org.guarneri.sap.pi;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.rmi.RemoteException;

import javax.ejb.EJBException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;

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.sdk.xi.lang.Binary;
import com.sap.aii.af.sdk.xi.mo.DefaultItem;
import com.sap.aii.af.sdk.xi.mo.MessageContext;
import com.sap.aii.af.service.administration.api.cpa.CPAFactory;
import com.sap.aii.af.service.administration.api.cpa.CPALookupManager;
import com.sap.aii.af.service.cpa.Binding;
import com.sap.engine.interfaces.messaging.api.Message;

// import com.sap.aii.adapter.xi.ms.XIMessage;

import com.sap.engine.interfaces.messaging.api.MessageDirection;
import com.sap.engine.interfaces.messaging.api.MessageKey;
import com.sap.engine.interfaces.messaging.api.PublicAPIAccess;
import com.sap.engine.interfaces.messaging.api.PublicAPIAccessFactory;
import com.sap.engine.interfaces.messaging.api.auditlog.AuditAccess;
import com.sap.engine.interfaces.messaging.api.auditlog.AuditLogStatus;
import com.sap.tc.logging.Location;
import com.sap.tc.logging.MsgObject;
import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;

/**
 * @author Ale
 * 
 */
public class SOAPSenderAlsoBean implements SessionBean, Module {

  final String SIG = “SOAPSenderAlsoBean – “;
  MessageKey key = null;
  PublicAPIAccess pa;
  AuditAccess audit;
  Message msg1;
  com.sap.aii.af.sdk.xi.mo.Message msg2;
  DefaultItem defaultItem;
  boolean commonMessage;

  boolean xmlDeclaration = true;

  private Location loc = null;

  public ModuleData process(ModuleContext moduleContext,
      ModuleData inputModuleData) throws ModuleException {

    loc = Location.getLocation(SOAPSenderAlsoBean.class.getName());
    loc.entering(SIG, new Object[] { moduleContext, inputModuleData });

    // Recognizing Adapter type
    String adapterType = null;
    try {
      String chid = moduleContext.getChannelID();
      CPALookupManager lm = CPAFactory.getInstance().getLookupManager();
      Binding b = lm.getBindingByChannelId(chid);
      adapterType = b.getAdapterType();
    } catch (Exception e) {
      throw new ModuleException(
          “Couldn’t recognize Adapter Type due to: ” + e.getMessage(),
          e);
    }

    // Handle usual basic stuff: get the Message and the Audit instance
    InputStream is = null;
    Object obj = inputModuleData.getPrincipalData();

    if (obj instanceof com.sap.aii.af.sdk.xi.mo.MessageContext) {
      commonMessage = false;
      MessageContext principalData = (MessageContext) obj;
      msg2 = principalData.getMessage();
    } else {
      commonMessage = true;
      msg1 = (Message) inputModuleData.getPrincipalData();
      is = msg1.getDocument().getInputStream();
    }

    if (!commonMessage) { // SOAP Sender
      defaultItem = (DefaultItem) msg2.getBodyItems().nextElement();
      try {
        Binary bin = defaultItem.getDataAsBinary();
        is = new ByteArrayInputStream(bin.getBytes());
      } catch (Exception e) {
        loc.error(“”, new MsgObject(“001”,
            “Error getting SOAP sender Request message: “
                + e.getMessage()));
      }
    }

    try {
      if (commonMessage) {
        if (msg1.getMessageDirection() == MessageDirection.OUTBOUND)
          key = new MessageKey(msg1.getMessageId(),
              MessageDirection.OUTBOUND);
        else
          key = new MessageKey(msg1.getMessageId(),
              MessageDirection.INBOUND);
      } else {
        key = (MessageKey) inputModuleData
            .getSupplementalData(“audit.key”); // SOAP Sender
      }
      pa = PublicAPIAccessFactory.getPublicAPIAccess();
      audit = pa.getAuditAccess();
    } catch (Exception e) {
      throw new ModuleException(“Error creating basic instances: “
          + e.getMessage() + getStackTrace(e), e);
    }

    audit.addAuditLogEntry(key, AuditLogStatus.SUCCESS, SIG
        + “Process started – Adapter type: ” + adapterType);

    // ———————————————————-
    // Get module parameters
    // ———————————————————-
    String p1 = moduleContext.getContextData(“p1”);
    String p2 = moduleContext.getContextData(“p2”);

    // …

    // ———————————————————-
    // Parse input document
    // ———————————————————-
    Document d;
    try {
      d = parseDoc(is);
    } catch (Exception e) {
      throw new ModuleException(SIG
          + “Error while parsing and normalizing main document: “
          + e.getMessage() + getStackTrace(e), e);
    }
    audit.addAuditLogEntry(key, AuditLogStatus.SUCCESS, SIG
        + “Document implementation is ” + d.getClass().getName());

    // ———————————————————-
    // Do THE Module’s JOB!
    // ———————————————————-

    // …      
    
    // ———————————————————-
    // Finalize process by setting new payload
    // ———————————————————-
    try {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      XMLSerializer xs = new XMLSerializer();
      OutputFormat of = new OutputFormat(d, “UTF-8”, false);
      xs.setOutputFormat(of);
      xs.setOutputByteStream(baos);
      xs.serialize(d);
      if (commonMessage) {
        msg1.getDocument().setContent(baos.toByteArray());
        inputModuleData.setPrincipalData(msg1);
      } else {
        defaultItem.setData(new Binary(baos.toByteArray()));
      }
    } catch (Exception e) {
      throw new ModuleException(SIG + “Error while setting new payload: “
          + e.getMessage() + getStackTrace(e), e);
    }

    audit.addAuditLogEntry(key, AuditLogStatus.SUCCESS, SIG
        + “Process ended”);
    return inputModuleData;
  }

  public Document parseDoc(InputStream is) throws ModuleException {
    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory
          .newInstance();
      factory.setNamespaceAware(true);
      DocumentBuilder builder = factory.newDocumentBuilder();
      Document d = builder.parse(is);
      return d;
    } catch (Exception e) {
      throw new ModuleException(SIG + “Error parsing document”
          + e.getMessage() + getStackTrace(e), e);
    }
  }

  public String getStackTrace(Throwable t) {
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw, true);
    t.printStackTrace(pw);
    pw.flush();
    sw.flush();
    return sw.toString();
  }

  public void ejbActivate() throws EJBException, RemoteException {

  }

  public void ejbPassivate() throws EJBException, RemoteException {

  }

  public void ejbRemove() throws EJBException, RemoteException {

  }

  public void setSessionContext(SessionContext arg0) throws EJBException,
      RemoteException {

  }

  public void ejbCreate() throws javax.ejb.CreateException {

  }

}      

Java2html   
To report this post you need to login first.

12 Comments

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

    1. Michal Krawczyk
      Hi,

      >>>This should put an end to all discussions in SDN about “SOAP adapter can not contain modules” discussions..

      @Jai
      Please let’s not start confusing – the discussions were only about – using SAP adapter modules in SOAP sender, not about using own adapter modules
      which is possible as Alex has shown 🙂

      BTW
      Alex waiting for your blog about custom
      axis adapter module for SOAP adapter
      or are you letting me do this one ? hehe:)

      Good info blog as always 🙂

      Regards,
      Michal Krawczyk

      (0) 
      1. Alessandro Guarneri Post author
        Hi Michal,
        Sure will try to find sometime to publish that nice thing to handle SOAP Fault with Axis. I just need to find one free hour , which is getting harder and harder… 🙂

        Cheers,
        Alex

        (0) 
  1. Vijayashankar Konam
    Hi Alessandro,
    I am trying to find the SequenceID attribute for dynamic EOIO queue name update. But I am not able understand the class structure and where it is actually stored.

    Can you help me with the below questions:
    1. Since the message has not yet been converted to XI message, does it mean, it does not have sequence id set yet?
    2. When would this SOAP message actually be converted to XI message in the module chain?
    3. I am not able to find the sequence id as I could for the XI Message class.

    Your inputs are appreciated.

    Thanks,
    VJ

    (0) 
    1. Alessandro Guarneri Post author
      Hi VJ,
      You’re probably right then: as I said (and as far as I remember) there are some limitations in this hack of mine, one of the major being the one you highlight. The message isn’t yet a PI message, but just a SOAP message.
      I don’t know when (and if) actually you have a PI msg available in the module chain… Maybe you could try doing something intermediate in the module (like  writing some values in the payload) and then really work on EOIO stuff on the ABAP stack, where the http handler resides.

      Or, if you’re on 7.1, exploit SOAP direct sending to ABAP stack (search for it: I remember a blog I can’t find now, but basically your ws consumer will address PI Pipeline URL directly, instead of the j2ee SOAP servlet): in that case you can follow my original CBS blog once again, and use the ABAP-way, by copying the stadard ICF service and handler class. In the handler class, you can do anything you want, including driving your message to a specific EOIO queue.

      Cheers
      Alex

      (0) 
  2. Bas Bach
    Hi Alessandro,

    Thx for thsi beautiful log but I have a question. How can I determine the messageclass for the XI3.0 environment? This because as when I use : instanceof com.sap.aii.messaging.mo.MessageContext it’s not recognized as a SOAP message and therfore I cannot handle the message appropiate. Or where can I find the source for the SOAP adpater so I can find out myself

    Thx in advance

      Greetz
        Bas

    (0) 
    1. Vijayashankar Konam
      May be Alessandro could add to it..

      You should find the below jar on the XI 3.0 Box and look at the methods offered and the type of objects that those methods take or return. This is one way of doing it..

      com.sap.aii.adapter.soap.ejb.jar

      VJ

      (0) 
    2. Alessandro Guarneri Post author
      Hi Bas,
      I will ask to a colleague of mine who took care of the porting 3.0 -> 7.1 to paste a snippet of the “old” code as soon as I have chance. If you don’t here from me in the next 7 days, ping me back again! 😉

      Ciao

      Alex

      (0) 

Leave a Reply