Terminating processes with delphi

Windows processes can be terminated from a delphi application using Win32 API calls. To terminate processes not owned by the current user the SE_DEBUG_NAME privilege must be set for the current process.

All sample code must include unit TlHelp32.

Sample code for activating SE_DEBUG_NAME privilege

  1. function NTSetPrivilege(sPrivilege: string; bEnabled: Boolean): Boolean;
  2. var
  3. hToken: THandle;
  4. TokenPriv: TOKEN_PRIVILEGES;
  5. PrevTokenPriv: TOKEN_PRIVILEGES;
  6. ReturnLength: Cardinal;
  7. begin
  8. Result := True;
  9. // Only for Windows NT/2000/XP and later.
  10. if not (Win32Platform = VER_PLATFORM_WIN32_NT) then
  11. Exit;
  12.  
  13. Result := False;
  14.  
  15. // obtain the processes token
  16. if OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken) then
  17. begin
  18. try
  19. // Get the locally unique identifier (LUID) .
  20. if LookupPrivilegeValue(nil, PChar(sPrivilege),TokenPriv.Privileges[0].Luid) then
  21. begin
  22. TokenPriv.PrivilegeCount := 1; // one privilege to set
  23.  
  24. case bEnabled of
  25. True: TokenPriv.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
  26. False: TokenPriv.Privileges[0].Attributes := 0;
  27. end;
  28.  
  29. ReturnLength := 0; // replaces a var parameter
  30. PrevTokenPriv := TokenPriv;
  31.  
  32. // enable or disable the privilege
  33. AdjustTokenPrivileges(hToken, False, TokenPriv, SizeOf(PrevTokenPriv),PrevTokenPriv, ReturnLength);
  34. end;
  35. finally
  36. CloseHandle(hToken);
  37. end;
  38. end;
  39.  
  40. // test the return value of AdjustTokenPrivileges.
  41. Result := GetLastError = ERROR_SUCCESS;
  42. if not Result then
  43. raise Exception.Create(SysErrorMessage(GetLastError));
  44. end;

Sample code for terminating processes by name of executable file

  1. procedure Killprocess(Name:String);
  2. var
  3. PEHandle,hproc: cardinal;
  4. PE: ProcessEntry32;
  5. begin
  6. NTSetPrivilege(SE_DEBUG_NAME,True);
  7. PEHandle := CreateTOOLHelp32Snapshot(TH32cs_Snapprocess,0);
  8. if PEHandle <> Invalid_Handle_Value then
  9. begin
  10. PE.dwSize := Sizeof(ProcessEntry32);
  11. Process32first(PEHandle,PE);
  12.  
  13. repeat
  14. if Lowercase(PE.szExeFile) = Lowercase(Pchar(Name)) then
  15. begin
  16. hproc := openprocess(Process_Terminate,false,pe.th32ProcessID);
  17. TerminateProcess(hproc,0);
  18. closehandle(hproc);
  19. end;
  20. until Process32next(PEHandle,PE)=false;
  21. end;
  22. closehandle(PEHandle);
  23. end;

Load balancing with Apache 2.2 mod_proxy_ajp

The Apache 2.2 webserver has a module for proxiing AJP requests (mod_proxy_ajp). This module is delivered with the Apache webserver by default.

Activating modules

The following modules must be enabled to use the AJP proxy functionallity:

  • mod_proxy
  • mod_proxy_ajp
  • mod_proxy_balancer

To activate the modules uncomment the following lines in your httpd.conf configuration file (e.g. /opt/apache/conf/httpd.conf):

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so

Configuring modules

To configure the modules we create a new configuration file conf/ajp_proxy.conf in the apache directory and add the following line to our httpd.conf file:

Include conf/ajp_proxy.conf

First of all we put all configuration directives in <IfModule/> blocks to ensure that all needed modules are loaded:

<IfModule mod_proxy>
  <IfModule mod_proxy_ajp>
    <IfModule mod_proxy_balancer>
      # configuration of AJP proxy
    </IfModule>
  </IfModule>
</IfModule>

Creating Load Balancer Cluster

The load balancer cluster is created with the ProxyPass directive. The syntax for this directive is

ProxyPass <path> balancer://<name-of-cluster> <options>

The <path> argument stays for the logical path on the apache server, <name-of-cluster> for the name of your cluster and <options> for the options for this load balacer cluster (see documentation for description of options).

Example:

ProxyPass /myapp balancer://mycluster/myapp stickysession=JSESSIONID nofailover=On

In the next step we must define the workers for our cluster and your application server must support the JServ AJP protocol, e.g. Tomcat. For glassfish aka Sun Java System Application Server see my mod_jk tutorial for implementing the JServ protocol into the server.

The workers are definded into a <Proxy/> directive. The syntax for this directive is

<Proxy balancer://<name-of-cluster>
  BalancerMember ajp:<hostname>:<port> <options>
</Proxy>

You can define multiple workers in one proxy directive.

Example for two worker nodes:

<Proxy balancer://mycluster>
  BalancerMember ajp://node1.mydomain.com:8009 route=node1
  BalancerMember ajp://node2.mydomain.com:8009 route=node2
</Proxy>

ObjectListDataProvider Tutorial

This is a tutorial for using the ObjectListDataProvider in a Visual Web Project. A ObjectListDataProvider is useful if the underlaying data is from a datasource not supported by NetBeans. Some samples for such datasources are a JPA or hibernate connection, other legacy systems such SAP or Lotus Notes and files in various formats.

In this tutorial we use a text file with customer records as datasource for our application.

Creating the project

In the first step you create a normal VWP project named OldpSample:

Project settings OLDP

Address POJO

For data storage we use a simple POJO object. Right click the oldpsample package under «Source Packages» and select «New -> Java Class». Name the class Address.

Create Address class

  1. package oldpsample;
  2.  
  3. public class Address {
  4.  
  5. private String id;
  6. private String title;
  7. private String lastname;
  8. private String firstname;
  9. private String street;
  10. private String city;
  11. private String state;
  12. private String country;
  13.  
  14. /** Creates a new instance of Address */
  15. public Address() {
  16. }
  17.  
  18. public String getId() {
  19. return id;
  20. }
  21.  
  22. public void setId(String id) {
  23. this.id = id;
  24. }
  25.  
  26. public String getTitle() {
  27. return title;
  28. }
  29.  
  30. public void setTitle(String title) {
  31. this.title = title;
  32. }
  33.  
  34. public String getLastname() {
  35. return lastname;
  36. }
  37.  
  38. public void setLastname(String lastname) {
  39. this.lastname = lastname;
  40. }
  41.  
  42. public String getFirstname() {
  43. return firstname;
  44. }
  45.  
  46. public void setFirstname(String firstname) {
  47. this.firstname = firstname;
  48. }
  49.  
  50. public String getStreet() {
  51. return street;
  52. }
  53.  
  54. public void setStreet(String street) {
  55. this.street = street;
  56. }
  57.  
  58. public String getCity() {
  59. return city;
  60. }
  61.  
  62. public void setCity(String city) {
  63. this.city = city;
  64. }
  65.  
  66. public String getState() {
  67. return state;
  68. }
  69.  
  70. public void setState(String state) {
  71. this.state = state;
  72. }
  73.  
  74. public String getCountry() {
  75. return country;
  76. }
  77.  
  78. public void setCountry(String country) {
  79. this.country = country;
  80. }
  81.  
  82. }

Create AddressDataProvider

Now we create the class AddressDataProvider. We create a new class and derive it from ObjectListDataProvider. Right click the oldpsample package under «Source Packages» and select «New -> Java Class».

Create AdressDataProvider class

Our class stores all data in a ArrayList, so we need a member variable of type ArrayList. In the constructor we must tell the underlying ObjectListDataProvider where the data comes from (method setList) and of which type the data is (method setObjectType). We also need methods to load the data from file or stream.

  1. package oldpsample;
  2.  
  3. import com.sun.data.provider.impl.ObjectListDataProvider;
  4. import java.io.BufferedReader;
  5. import java.io.FileInputStream;
  6. import java.io.InputStream;
  7. import java.io.InputStreamReader;
  8. import java.util.ArrayList;
  9.  
  10. public class AddressDataProvider extends ObjectListDataProvider {
  11.  
  12. private ArrayList addressList = new ArrayList();
  13.  
  14. /** Creates a new instance of AddressDataProvider */
  15. public AddressDataProvider() {
  16. setList( addressList );
  17. setObjectType( Address.class );
  18. }
  19.  
  20. public void load(InputStream istream ) {
  21. try {
  22. InputStreamReader sr = new InputStreamReader( istream );
  23.  
  24. while ( br.ready() ) {
  25. String line = br.readLine();
  26. String[] cols = line.split( ";" );
  27.  
  28. if ( cols.length == 8 ) {
  29. Address address = new Address();
  30. address.setId( cols[0] );
  31. address.setTitle( cols[1] );
  32. address.setLastname( cols[2] );
  33. address.setFirstname( cols[3] );
  34. address.setStreet( cols[4] );
  35. address.setCity( cols[5] );
  36. address.setState( cols[6] );
  37. address.setCountry( cols[7] );
  38. getList().add( address );
  39. }
  40. }
  41.  
  42. } catch ( Exception e ) {
  43. e.printStackTrace();
  44. }
  45. }
  46.  
  47. public void load(String filename) {
  48. try {
  49. FileInputStream fs = new FileInputStream( filename );
  50. load( fs );
  51. } catch ( Exception e ) {
  52. e.printStackTrace();
  53. }
  54. }
  55.  
  56. }

Add AddressDataProvider to SessionBean1

In the outline view right click on SessionBean1 and select «Add->Property» from the context menu. Name the property «addressDataProvider» and enter «AddressDataProvider» as Type. Leave all other options on the default values.

New Property addressDataProvider

Double click SessionBean1 in the outline view. Find the line

  1. private AddressDataProvider addressDataProvider;

and change it to

  1. private AddressDataProvider addressDataProvider = new AddressDataProvider();

You must build, close and reopen your VWP project now (as of NetBeans 5.5.1). If not, NetBeans doesn't detect our ObjectListDataProvider. This should hopefully not been necessary in future NetBeans releases.

Design Web Page

The project wizard has generated a default web page Page1.jsp. Open this page and the visual designer starts.

Select a table component from the palette and drop it onto your page. Right click the table component and select «Bind to Data...». Select «addressDataProvider (SessionBean1)» from the dropdown list. Reorder the fields with the «Up» and «Down» buttons, so it look like this:

Select Data Provider

Press «OK» and our page has a table component bound to the AddressDataProvider.

To show some data in the table component we let the user upload a CSV file. Drop a File Upload and a Button component from the palette onto the page. Set the text property of the button to «Upload file». Your page should look like this:

Page Design

Double click on the «Upload file» button and enter the following code into the event handler:

  1. public String button1_action() {
  2. if ( fileUpload1.getUploadedFile().getSize() > 0 ) {
  3. try {
  4. getSessionBean1().getAddressDataProvider().load(
  5. getFileUpload1().getUploadedFile().getInputStream() );
  6. } catch ( Exception e ) {
  7. e.printStackTrace();
  8. }
  9. }
  10. return null;
  11. }

Run your project. Use this sample CSV data file to test the application:

1;Mr.;Able;Tony;216 King St;San Francisco;CA;USA
2;Mr.;Black;Tom;655 Divisadero St;San Francisco;CA;USA
3;Mr.;Kent;Richard;509 Valencia St;San Francisco;CA;USA
4;Mr.;Chen;Larry;407 Ellis St;San Francisco;CA;USA
5;Mrs.;Donaldson;Sue;314 Columbus Ave;San Francisco;CA;USA
6;Mr.;Murrell;Tony;4124 Geary Blvd;San Francisco;CA;USA

This sample project can also be downloaded.

Use Tomcat Connector for Load Balancing Glassfish or SJSAS

In this tutorial i describe how you can use the Tomcat Connector (mod_jk module) as a load balancer for the Glassfish or Sun Java System Application Server (SJSAS). It's availabe as a loadable module for the Apache webserver and for the Microsoft IIS.

The Tomcat Connector uses the packet oriented binary AJP13 protocol for the communication between the servlet container and the webserver. This architecture is optimized for speed and is much faster as a proxy configuration.

Download components

  1. Download the actual mod_jk distribution for either Apache or IIS webserver.
  2. Download the actual Tomcat 5.5 distribution.
  3. Download commons-logging and commons-modeler from the Jakarta Project.

Installation for Apache Webserver

Copy the mod_jk distribution files to your apache libexec directory, e.g. /srv/apache/libexec under Linux OS.

Edit the server configuration file httpd.conf and add the following configuration options:

# Load mod_jk module
LoadModule jk_module libexec/mod_jk.so
AddModule mod_jk.c

<IfModule mod_jk.c>
  # Tells the module the location of the workers.properties file
  JkWorkersFile /srv/apache/conf/workers.properties

  # Specifies the location for this module's specific log file
  JkLogFile /var/log/mod_jk.log

  # Sets the module's log level to info
  JkLogLevel info

  # Sets the module's log time stamp format
  JkLogStampFormat "[%a %b %d %H:%M:%S %Y] "

  # JkOptions for SSL
  JkOptions +ForwardKeySize +ForwardURICompat -ForwardDirectories

  # Set mount points for load balancer
  JkMount /app1   loadbalancer1
  JkMount /app1/* loadbalancer1
  JkMount /app2   loadbalancer1
  JkMount /app2/* loadbalancer1
</IfModule>

Installation for Microsoft IIS

  1. In the registry, create a new registry key named "HKEY_LOCAL_MACHINE\SOFTWARE\Apache Software Foundation\Jakarta Isapi Redirector\1.0"
  2. Add a string value with the name extension_uri and a value of /jakarta/isapi_redirect.dll.
  3. Add a string value with the name log_file and a value pointing to where you want your log file to be (for example c:\mod_jk\isapi.log).
  4. Add a string value with the name log_level and a value for your log level (can be debug, info, error or emerg).
  5. Add a string value with the name worker_file and a value which is the full path to your workers.properties file (for example c:\mod_jk\workers.properties)
  6. Add a string value with the name worker_mount_file and a value which is the full path to your uriworkermap.properties file (for example c:\mod_jk\uriworkermap.properties)
  7. Using the IIS management console, add a new virtual directory to your IIS/PWS web site. The name of the virtual directory must be jakarta. Its physical path should be the directory where you placed isapi_redirect.dll (in our example it is c:\mod_jk). While creating this new virtual directory assign it with execute access.
  8. Using the IIS management console, add isapi_redirect.dll as a filter in your IIS/PWS web site. The name of the filter should reflect its task (I use the name glassfish), its executable must be our c:\mod_jk\isapi_redirect.dll. For PWS, you'll need to use regedit and add/edit the "Filter DLLs" key under HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\W3SVC\Parameters. This key contains a "," separated list of dlls (full paths) - you need to insert the full path to isapi_redirect.dll.
  9. Restart IIS (stop + start the IIS service), make sure that the tomcat filter is marked with a green up-pointing arrow. Under Win98 you may need to cd WINDOWS\SYSTEM\inetsrv and type PWS /stop ( the DLL and log files are locked - even if you click the stop button, PWS will still keep the DLLs in memory. ). Type pws to start it again.

Setup uriworkermap.properties (IIS only)

In the uriworkermap.properties file you define the mappings for URL's which should be connected to your load balancer:

# Mapping for app1
/app1=loadbalancer1
/app1/*=loadbalancer1

# Mapping for app2
/app2=loadbalancer1
/app2/*=loadbalancer1

Configuration of Tomcat Connector

Edit the file workers.properties which you have specified in your webserver configuration file:

# Define all worker nodes
worker.list=loadbalancer1

# Set properties for server1 (ajp13)
worker.server1.type=ajp13
worker.server1.host=server1
worker.server1.port=8009
worker.server1.lbfactor=50
worker.server1.cachesize=10
worker.server1.cache_timeout=600
worker.server1.socket_keepalive=1
worker.server1.socket_timeout=300

# Set properties for server2 (ajp13)
worker.server2.type=ajp13
worker.server2.host=server2
worker.server2.port=8009
worker.server2.lbfactor=50
worker.server2.cachesize=10
worker.server2.cache_timeout=600
worker.server2.socket_keepalive=1
worker.server2.socket_timeout=300

# Set properties for loadbalancer1
worker.loadbalancer1.type=lb
worker.loadbalancer1.balance_workers=server1,server2
worker.loadbalancer1.sticky_session=true
worker.loadbalancer1.sticky_session_force=false
worker.loadbalancer1.method=request
worker.loadbalancer1.lock=optimistic
worker.loadbalancer1.retries=3

Restart your Apache webserver.

Installing AJP13 connector into application server

To install the AJP13 connector into your application server you must copy the file

$TOMCAT_HOME/server/lib/tomcat_ajp.jar

from the Tomcat server distribution to your application server's lib directory. It seems that the actual Tomcat 5.5.25 tomcat_ajp.jar doesn't work and throws a java.lang.NoSuchMethodError exception. So use the library bundled with Tomcat 5.5.16 and all works ok. You also need to copy the downloaded commons-logging.jar and commons-modeler.jar to this directory.

To activate the connector use the console application to add the following JVM option to your server instance:

-Dcom.sun.enterprise.web.connector.enableJK=8009

Restart your application server.

All requests to the definded mount points on the webserver should now be delegated to one of your application servers.

You can download the sample configuration files, jar archives and mod_jk modules for windows and linux here.

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:

  1. package de.gascoyne.mapper;
  2.  
  3. import com.sap.aii.mapping.api.StreamTransformation;
  4. import com.sap.aii.mapping.api.StreamTransformationConstants;
  5. import com.sap.aii.mapping.api.StreamTransformationException;
  6. import java.io.InputStream;
  7. import java.io.OutputStream;
  8. import java.io.PrintWriter;
  9. import java.util.Iterator;
  10. import java.util.Map;
  11. import org.jdom.Document;
  12. import org.jdom.Element;
  13. import org.jdom.input.SAXBuilder;
  14.  
  15. /**
  16.  * Sample mapper for SAP-XI
  17.  * @author Marcel Gascoyne
  18.  */
  19. public class AddressToTextMapper implements StreamTransformation {
  20.  
  21. private String party = "";
  22.  
  23. /**
  24.   * Injection of mapping parameters
  25.   * from integration engine
  26.   *
  27.   * @param map Map with configuration data
  28.   */
  29. public void setParameter(Map map) {
  30. // Determine receiver party
  31. party = (String) map.get(
  32. StreamTransformationConstants.RECEIVER_PARTY );
  33. }
  34.  
  35. /**
  36.   * Mapping implementation
  37.   *
  38.   * @param inputStream Input data from integration engine
  39.   * @param outputStream Output data to integration engine
  40.   */
  41. public void execute(InputStream inputStream,
  42. OutputStream outputStream)
  43. throws StreamTransformationException {
  44. try {
  45. // get idoc from input stream
  46. SAXBuilder sb = new SAXBuilder();
  47. Document doc = sb.build( inputStream );
  48. Element idoc = doc.getRootElement().getChild( "IDOC" );
  49.  
  50. // create PrintWriter for output stream
  51. PrintWriter out = new PrintWriter( outputStream );
  52. String header = "party;vkorg;title;lastname;firstname;" +
  53. "street;streetno;city;postlcode;state;country\n";
  54. out.print( header );
  55.  
  56. // process all ZADDRESS segments
  57. for ( Iterator it = idoc.getChildren().iterator(); it.hasNext(); ) {
  58. Element element = (Element) it.next();
  59.  
  60. if ( !element.getName().equals( "ZADDRESS" ) ) {
  61. continue;
  62. }
  63.  
  64. // get data from segment ZADDRESS
  65. String vkorg = element.getChildText( "VKORG" );
  66. String title = element.getChildText( "TITLE" );
  67. String lastname = element.getChildText( "LASTNAME" );
  68. String firstname = element.getChildText( "FIRSTNAME" );
  69. String street = element.getChildText( "STREET" );
  70. String streetNo = element.getChildText( "STREETNO" );
  71. String city = element.getChildText( "CITY" );
  72. String postlCode = element.getChildText( "POSTLCODE" );
  73. String state = element.getChildText( "STATE" );
  74. String country = element.getChildText( "COUNTRY" );
  75.  
  76. // create output line
  77. String line =
  78. vkorg + ";" +
  79. title + ";" +
  80. lastname + ";" +
  81. firstname + ";" +
  82. street + ";" +
  83. streetNo + ";" +
  84. city + ";" +
  85. postlCode + ";" +
  86. state + ";" +
  87. country + "\r\n";
  88.  
  89. // Write line to output stream
  90. out.print( line );
  91.  
  92. }
  93.  
  94. out.flush();
  95. out.close();
  96.  
  97. } catch ( Exception e ) {
  98. throw new StreamTransformationException( e.getMessage() );
  99. }
  100. }
  101.  
  102. }

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:

  1. package de.gascoyne.mapper;
  2.  
  3. import com.sap.aii.mapping.api.StreamTransformation;
  4. import com.sap.aii.mapping.api.StreamTransformationConstants;
  5. import com.sap.aii.mapping.api.StreamTransformationException;
  6. import java.io.BufferedReader;
  7. import java.io.InputStream;
  8. import java.io.InputStreamReader;
  9. import java.io.OutputStream;
  10. import java.io.PrintWriter;
  11. import java.text.SimpleDateFormat;
  12. import java.util.Date;
  13. import java.util.Map;
  14. import org.jdom.Document;
  15. import org.jdom.Element;
  16. import org.jdom.output.XMLOutputter;
  17.  
  18. /**
  19.  * Sample mapper for SAP-XI
  20.  * @author Marcel Gascoyne
  21.  */
  22. public class FileToIdocMapper implements StreamTransformation {
  23.  
  24. private String party = "";
  25.  
  26. /**
  27.   * Injection of mapping parameters
  28.   * from integration engine
  29.   *
  30.   * @param map Map with configuration data
  31.   */
  32. public void setParameter(Map map) {
  33. // Determine receiver party
  34. party = (String) map.get(
  35. StreamTransformationConstants.RECEIVER_PARTY );
  36. }
  37.  
  38. /**
  39.   * Mapping implementation
  40.   *
  41.   * @param inputStream Input data from integration engine
  42.   * @param outputStream Output data to integration engine
  43.   */
  44. public void execute(InputStream inputStream,
  45. OutputStream outputStream)
  46. throws StreamTransformationException {
  47. try {
  48. InputStreamReader ir = new InputStreamReader( inputStream, "ISO-8859-1" );
  49.  
  50. // create control record
  51. SimpleDateFormat df = new SimpleDateFormat( "yyyyMMddHHmmss" );
  52. Element edi_dc40 = new Element( "EDI_DC40" );
  53. edi_dc40.setAttribute( "SEGMENT", "1" );
  54. edi_dc40.addContent( new Element( "TABNAM" ).addContent( "empty" ) );
  55. edi_dc40.addContent( new Element( "DIRECT" ).addContent( "empty" ) );
  56. edi_dc40.addContent( new Element( "IDOCTYP" ).addContent( "empty" ) );
  57. edi_dc40.addContent( new Element( "MESTYP" ).addContent( "empty" ) );
  58. edi_dc40.addContent( new Element( "SNDPOR" ).addContent( "empty" ) );
  59. edi_dc40.addContent( new Element( "SNDPRT" ).addContent( "empty" ) );
  60. edi_dc40.addContent( new Element( "SNDPRN" ).addContent( "empty" ) );
  61. edi_dc40.addContent( new Element( "RCVPOR" ).addContent( "empty" ) );
  62. edi_dc40.addContent( new Element( "RCVPRN" ).addContent( "empty" ) );
  63. edi_dc40.addContent( new Element( "SERIAL" ).addContent( df.format( new Date() ) ) );
  64.  
  65. // create idoc
  66. Document zaddress01 = new Document();
  67. Element root = new Element( "ZADDRESS01" );
  68. zaddress01.setRootElement( root );
  69. Element idoc = new Element( "IDOC" );
  70. idoc.setAttribute( "BEGIN", "1" );
  71. idoc.addContent( edi_dc40 );
  72. root.addContent( idoc );
  73.  
  74. while ( br.ready() ) {
  75. // get line of input stream
  76. String line = br.readLine();
  77.  
  78. // check for header line
  79. if ( line.startsWith( "party;vkorg;" ) ) {
  80. continue;
  81. }
  82.  
  83. // split values by semicolon in array
  84. String[] columns = line.split( ";" );
  85.  
  86. // create ZADDRESS segment
  87. Element zaddress = new Element( "ZADDRESS" );
  88. zaddress.setAttribute( "SEGMENT", "1" );
  89. zaddress.addContent( new Element( "VKORG" ).setText( columns[1] ) );
  90. zaddress.addContent( new Element( "TITLE" ).setText( columns[2] ) );
  91. zaddress.addContent( new Element( "LASTNAME" ).setText( columns[3] ) );
  92. zaddress.addContent( new Element( "FIRSTNAME" ).setText( columns[4] ) );
  93. zaddress.addContent( new Element( "STREET" ).setText( columns[5] ) );
  94. zaddress.addContent( new Element( "CITY" ).setText( columns[6] ) );
  95. zaddress.addContent( new Element( "POSTLCODE" ).setText( columns[7] ) );
  96. zaddress.addContent( new Element( "STATE" ).setText( columns[8] ) );
  97. zaddress.addContent( new Element( "COUNTRY" ).setText( columns[9] ) );
  98.  
  99. // add segment to idoc
  100. idoc.addContent( zaddress );
  101. }
  102.  
  103. // write idoc to output stream
  104. PrintWriter out = new PrintWriter( outputStream );
  105. XMLOutputter xout = new XMLOutputter();
  106. xout.output( zaddress01, out );
  107.  
  108. } catch ( Exception e ) {
  109. throw new StreamTransformationException( e.getMessage() );
  110. }
  111. }
  112.  
  113. }

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:

  1. package de.gascoyne.mapper;
  2.  
  3. import com.sap.aii.mapping.api.StreamTransformationConstants;
  4. import java.io.FileInputStream;
  5. import java.io.FileOutputStream;
  6. import junit.framework.*;
  7. import java.io.InputStream;
  8. import java.io.OutputStream;
  9. import java.util.HashMap;
  10. import java.util.Map;
  11.  
  12. /**
  13.  * TestCase for AddressToTextMapper
  14.  * @author Marcel Gascoyne
  15.  */
  16. public class AddressToTextMapperTest extends TestCase {
  17.  
  18. public void testExecute() throws Exception {
  19. InputStream in = new FileInputStream( "build/test/classes/idoc_in.xml" );
  20. OutputStream out = new FileOutputStream( "build/test/results/text_out.txt" );
  21. AddressToTextMapper instance = new AddressToTextMapper();
  22. Map params = new HashMap();
  23.  
  24. params.put( StreamTransformationConstants.RECEIVER_PARTY, "PARTY1" );
  25. instance.setParameter( params );
  26. instance.execute( in, out );
  27. }
  28.  
  29. }