Developing Java Mappings for SAP XI

You can use NetBeans to develop java mappings for the SAP NetWeaver Exchange Infrastructure (SAP XI). The only thing you need is the mapping api library from your SAP XI installation.

Registering Mapping API in NetBeans

Use the NetBeans Library Manager under «Tools->Library Manager» to register the aii_map_api.jar file. You find this library in the following path of your SAP XI installation:

<SAP_install_dir>/<system_name>/<instance_name>/j2ee/cluster/
server<number>/apps/sap.com/com.sap.xi.services/

Project settings

The SAP NetWeaver Exchange Infrastructure 3.0 is based on J2EE 1.3 and Java 1.4, so you must use a JDK 1.4.x and source level 1.4 for your project.

Useful libraries

SAP XI uses XML messages for message exchange. You can eather use the standard XML api libraries shipped with the JDK or you can use the pretty nice JDom open source library. I preffer JDom, because it has a build-in SAX builder and can also output XML as text (also formatted).

Mapping class

Your mapping class must implement the com.sap.aii.mapping.api.StreamTransformation interface. This interface requires you to implement the execute() and setParameter() methods.

Method execute()

The execute() method does the whole mapping. This method has two parameters of type java.io.InputStream and java.io.OutputStream. You read data from the input stream, make the necessary mapping and write the resulting data to the output stream.

SAP XI usually uses XML messages, but you can also read and write other type of data in your mapping, e.g. comma-separated values.

Method setParameter()

The integration engine uses the method setParameter() to inform the mapper about runtime configuration parameters. You can use the constants of the interface com.sap.aii.mapping.api.StreamTransformationConstants to access this parameters.

Sample mapping class for IDOC to file scenario

In this sample we map an IDOC to a plain text file.

XML representation of this IDOC

<?xml version="1.0" encoding="UTF-8" ?>
<ZADDRESS01>
  <IDOC BEGIN="1">
    <EDI_DC40 SEGMENT="1">
      <TABNAM>EDI_DC40</TABNAM>
      <MANDT>100</MANDT>
      <DOCNUM>0000000000000001</DOCNUM>
      <DOCREL>620</DOCREL>
      <STATUS>30</STATUS>
      <DIRECT>1</DIRECT>
      <OUTMOD>4</OUTMOD>
      <IDOCTYP>ZADDRESS01</IDOCTYP>
      <MESTYP>ZADDRESS</MESTYP>
      <MESCOD>DEV</MESCOD>
      <MESFCT>OUT</MESFCT>
      <SNDPOR>SAPDEV</SNDPOR>
      <SNDPRT>LS</SNDPRT>
      <SNDPRN>DEVCLNT100</SNDPRN>
      <RCVPOR>XID</RCVPOR>
      <RCVPRT>LS</RCVPRT>
      <RCVPRN>PARTNER1</RCVPRN>
      <CREDAT>20071028</CREDAT>
      <CRETIM>101658</CRETIM>
      <SERIAL>20071028101657</SERIAL>
    </EDI_DC40>
    <ZADDRESS SEGMENT="1">
      <VKORG>1000</VKORG>
      <TITLE>Mr.</TITLE>
      <LASTNAME>Doe</LASTNAME>
      <FIRSTNAME>John</FIRSTNAME>
      <STREET>Pier</STREET>
      <STREETNO>47</STREETNO>
      <CITY>San Francisco</CITY>
      <POSTLCODE>94133</POSTLCODE>
      <STATE>CA</STATE>
      <COUNTRY>US</COUNTRY>
    </ZADDRESS>
    <ZADDRESS SEGMENT="1">
      <VKORG>1000</VKORG>
      <TITLE>Mrs.</TITLE>
      <LASTNAME>Doe</LASTNAME>
      <FIRSTNAME>Jane</FIRSTNAME>
      <STREET>Mason St.</STREET>
      <STREETNO>950</STREETNO>
      <CITY>San Francisco</CITY>
      <POSTLCODE>94108</POSTLCODE>
      <STATE>CA</STATE>
      <COUNTRY>US</COUNTRY>
    </ZADDRESS>
  </IDOC>
</ZADDRESS01>

Source code of mapper:

package de.gascoyne.mapper;

import com.sap.aii.mapping.api.StreamTransformation;
import com.sap.aii.mapping.api.StreamTransformationConstants;
import com.sap.aii.mapping.api.StreamTransformationException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.Map;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;

/**
 * Sample mapper for SAP-XI
 * @author Marcel Gascoyne
 */
public class AddressToTextMapper implements StreamTransformation {

  private String party = "";

  /**
   * Injection of mapping parameters
   * from integration engine
   *
   * @param map Map with configuration data
   */
  public void setParameter(Map map) {
    // Determine receiver party
    party = (String) map.get(
      StreamTransformationConstants.RECEIVER_PARTY );
  }

  /**
   * Mapping implementation
   *
   * @param inputStream Input data from integration engine
   * @param outputStream Output data to integration engine
   */
  public void execute(InputStream inputStream,
    OutputStream outputStream)
    throws StreamTransformationException {
    try {
      // get idoc from input stream
      SAXBuilder sb = new SAXBuilder();
      Document doc = sb.build( inputStream );
      Element idoc = doc.getRootElement().getChild( "IDOC" );

      // create PrintWriter for output stream
      PrintWriter out = new PrintWriter( outputStream );
      String header = "party;vkorg;title;lastname;firstname;" +
        "street;streetno;city;postlcode;state;country\n";
      out.print( header );

      // process all ZADDRESS segments
      for ( Iterator it = idoc.getChildren().iterator(); it.hasNext(); ) {
        Element element = (Element) it.next();

        if ( !element.getName().equals( "ZADDRESS" ) ) {
          continue;
        }

        // get data from segment ZADDRESS
        String vkorg = element.getChildText( "VKORG" );
        String title = element.getChildText( "TITLE" );
        String lastname = element.getChildText( "LASTNAME" );
        String firstname = element.getChildText( "FIRSTNAME" );
        String street = element.getChildText( "STREET" );
        String streetNo = element.getChildText( "STREETNO" );
        String city = element.getChildText( "CITY" );
        String postlCode = element.getChildText( "POSTLCODE" );
        String state = element.getChildText( "STATE" );
        String country = element.getChildText( "COUNTRY" );

        // create output line
        String line =
          vkorg + ";" +
          title + ";" +
          lastname + ";" +
          firstname + ";" +
          street + ";" +
          streetNo + ";" +
          city + ";" +
          postlCode + ";" +
          state + ";" +
          country + "\r\n";

        // Write line to output stream
        out.print( line );

      }

      out.flush();
      out.close();

    } catch ( Exception e ) {
      throw new StreamTransformationException( e.getMessage() );
    }
  }

}

Resulting text file:

party;vkorg;title;lastname;firstname;street;streetno;city;postlcode;state;country
PARTNER1;1000;Mr.;Doe;John;Pier;47;San Francisco;94133;CA;US
PARTNER1;1000;Mrs.;Doe;Jane;Mason St.;950;San Francisco;94108;CA;US

Sample mapping class for file to IDOC scenario

Now the opposite way. In this scenario we map a text file to an IDOC. We must create the whole IDOC structure from scratch in XML format. Many fields in the IDOC control record are mandantory fields and must be set, although the integration engine injects the correct values in this fields. We set this fields to the static text empty and the integration engine do the rest.

Input text file:

party;vkorg;title;lastname;firstname;street;streetno;city;postlcode;state;country
1000;Mr.;Doe;John;Pier;47;San Francisco;94133;CA;US
1000;Mrs.;Doe;Jane;Mason St.;950;San Francisco;94108;CA;US

Source code of java mapper:

package de.gascoyne.mapper;

import com.sap.aii.mapping.api.StreamTransformation;
import com.sap.aii.mapping.api.StreamTransformationConstants;
import com.sap.aii.mapping.api.StreamTransformationException;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.XMLOutputter;

/**
 * Sample mapper for SAP-XI
 * @author Marcel Gascoyne
 */
public class FileToIdocMapper implements StreamTransformation {

  private String party = "";

  /**
   * Injection of mapping parameters
   * from integration engine
   *
   * @param map Map with configuration data
   */
  public void setParameter(Map map) {
    // Determine receiver party
    party = (String) map.get(
      StreamTransformationConstants.RECEIVER_PARTY );
  }

  /**
   * Mapping implementation
   *
   * @param inputStream Input data from integration engine
   * @param outputStream Output data to integration engine
   */
  public void execute(InputStream inputStream,
    OutputStream outputStream)
    throws StreamTransformationException {
    try {
      InputStreamReader ir = new InputStreamReader( inputStream, "ISO-8859-1" );
      BufferedReader br = new BufferedReader( ir );

      // create control record
      SimpleDateFormat df = new SimpleDateFormat( "yyyyMMddHHmmss" );
      Element edi_dc40 = new Element( "EDI_DC40" );
      edi_dc40.setAttribute( "SEGMENT", "1" );
      edi_dc40.addContent( new Element( "TABNAM" ).addContent( "empty" ) );
      edi_dc40.addContent( new Element( "DIRECT" ).addContent( "empty" ) );
      edi_dc40.addContent( new Element( "IDOCTYP" ).addContent( "empty" ) );
      edi_dc40.addContent( new Element( "MESTYP" ).addContent( "empty" ) );
      edi_dc40.addContent( new Element( "SNDPOR" ).addContent( "empty" ) );
      edi_dc40.addContent( new Element( "SNDPRT" ).addContent( "empty" ) );
      edi_dc40.addContent( new Element( "SNDPRN" ).addContent( "empty" ) );
      edi_dc40.addContent( new Element( "RCVPOR" ).addContent( "empty" ) );
      edi_dc40.addContent( new Element( "RCVPRN" ).addContent( "empty" ) );
      edi_dc40.addContent( new Element( "SERIAL" ).addContent( df.format( new Date() ) ) );

      // create idoc
      Document zaddress01 = new Document();
      Element root = new Element( "ZADDRESS01" );
      zaddress01.setRootElement( root );
      Element idoc = new Element( "IDOC" );
      idoc.setAttribute( "BEGIN", "1" );
      idoc.addContent( edi_dc40 );
      root.addContent( idoc );

      while ( br.ready() ) {
        // get line of input stream
        String line = br.readLine();

        // check for header line
        if ( line.startsWith( "party;vkorg;" ) ) {
          continue;
        }

        // split values by semicolon in array
        String[] columns = line.split( ";" );

        // create ZADDRESS segment
        Element zaddress = new Element( "ZADDRESS" );
        zaddress.setAttribute( "SEGMENT", "1" );
        zaddress.addContent( new Element( "VKORG" ).setText( columns[1] ) );
        zaddress.addContent( new Element( "TITLE" ).setText( columns[2] ) );
        zaddress.addContent( new Element( "LASTNAME" ).setText( columns[3] ) );
        zaddress.addContent( new Element( "FIRSTNAME" ).setText( columns[4] ) );
        zaddress.addContent( new Element( "STREET" ).setText( columns[5] ) );
        zaddress.addContent( new Element( "CITY" ).setText( columns[6] ) );
        zaddress.addContent( new Element( "POSTLCODE" ).setText( columns[7] ) );
        zaddress.addContent( new Element( "STATE" ).setText( columns[8] ) );
        zaddress.addContent( new Element( "COUNTRY" ).setText( columns[9] ) );

        // add segment to idoc
        idoc.addContent( zaddress );
      }

      // write idoc to output stream
      PrintWriter out = new PrintWriter( outputStream );
      XMLOutputter xout = new XMLOutputter();
      xout.output( zaddress01, out );

    } catch ( Exception e ) {
      throw new StreamTransformationException( e.getMessage() );
    }
  }

}

Unit tests

To locally test your java mappings, you can create JUnit tests inside the NetBeans IDE. From within your mapping class select «Tools->Create JUnit Tests» to create a test case for your mapper.

This is a sample test case for an idoc-to-file mapper:

package de.gascoyne.mapper;

import com.sap.aii.mapping.api.StreamTransformationConstants;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import junit.framework.*;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * TestCase for AddressToTextMapper
 * @author Marcel Gascoyne
 */
public class AddressToTextMapperTest extends TestCase {

  public void testExecute() throws Exception {
    InputStream in = new FileInputStream( "build/test/classes/idoc_in.xml" );
    OutputStream out = new FileOutputStream( "build/test/results/text_out.txt" );
    AddressToTextMapper instance = new AddressToTextMapper();
    Map params = new HashMap();

    params.put( StreamTransformationConstants.RECEIVER_PARTY, "PARTY1" );
    instance.setParameter( params );
    instance.execute( in, out );
  }

}