Android Client App for consuming SAP SOAP Web services using ksoap2 library

Recently SAP lauched their SAP Mobile Platform 3.0 (more about that here ) and I decide to write about how one can develop an Android Client App that can consume SAP SOAP Web Services with the ksoap2 library. I am maybe a bit late but it still deserves some attention.

Even though recently I had the chance to try out for myself the SAP Mobile Platform 2.2 and develop some Demo Apps testing both MBO and OData concepts I still personally feel that there are many people out there that are still developing mobile apps for SAP the old-fashion-way: ask for their SAP ABAP developers to prepare a couple of web services and develop mobile clients for these.

One point is that many companies still haven’t updated their Netweaver Gateways, so they can not use the OData goodies that come along with the newer versions. Another reason is that a change of mindset is needed and acceptance of the new web technologies and REST as an architecture.  And lest but not least is that there is still not a stable concept about the offline data management. MBOs were built with keeping that in mind, but SAP has decided not to support it anymore as it was a propriatery “protocol”. The Delta Query support that they mention sounds promising here but I have to try it out in order to judge it 🙂

I have one very old post about consuming SAP web services (or it should be consuming .NET web services that call RFC-enabled Function Modules from an SAP-Backend) where the landscape I had for development was SAP Backend, Visual Studio and SAP .NET connector library. Well, this time I had SAP Backend with enabled Webservices Runtime and as any normal developer would do: I made the use of the web services that you can build with SAP from any Remote-enabled Function module and develop clients that would consume this services. The best library out there that one can make use of at the moment is ksoap2 and since it is free you can use it and adapt it to your needs. I actually came to the idea of this blog post after reading many forums where people complained that they didn’t manage to get it work or they had to change the source and rebuild it after changing the names of the tags or something in the XML bulding logic.

How am I using the ksoap2 library to consume SAP Web services? Very elegant and smooth actually.

1. I developed an wrapper class for the SAPSerializationEnvelope that inherits from the SoapSerializationEnvelope where I just make it create XML-Requests in the SAP manner.

import java.io.IOException;
import org.ksoap2.SoapFault;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.xmlpull.v1.XmlSerializer;

public class SAPSerializationEnvelope extends SoapSerializationEnvelope {

public String namespace;
 public SAPSerializationEnvelope(int version, String namespace) {
 super(version);
 this.namespace= namespace;
 }

 @Override
 public void write(XmlSerializer writer) throws IOException {

writer.setPrefix("urn", namespace);
 writer.setPrefix("soapenv", env);
 writer.startTag(env, "Envelope");
 writer.startTag(env, "Header");
 writeHeader(writer);
 writer.endTag(env, "Header");
 writer.startTag(env, "Body");
 writeBody(writer);
 writer.endTag(env, "Body");
 writer.endTag(env, "Envelope");

 }

@Override
 public Object getResponse() throws SoapFault {
 return super.getResponse();
 }

}

2. Made a helper class for making calls.

import java.util.ArrayList;
import java.util.List;
import org.ksoap2.HeaderProperty;
import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;

public class SAPServiceCaller {

String NAMESPACE_SOAP = "http://schemas.xmlsoap.org/soap/envelope/";
 static String NAMESPACE = "urn:sap-com:document:sap:soap:functions:mc-style";
 static String SOAP_ACTION = "";

 public static SoapObject webServiceCall(SoapObject request, String URL, boolean imp_types){
 SoapObject response = null;

 SAPSerializationEnvelope envelope = new SAPSerializationEnvelope(
 SoapEnvelope.VER11, NAMESPACE);
 envelope.implicitTypes = imp_types;
 envelope.dotNet = false;
 envelope.setAddAdornments(false);
 envelope.env = SoapSerializationEnvelope.ENV;

 envelope.setOutputSoapObject(request);

 HttpTransportSE androidHttpTransport = new HttpTransportSE(URL, 30000);
 try {
 List<HeaderProperty> headerList = new ArrayList<HeaderProperty>();
 headerList.add(new HeaderProperty("Authorization", "Basic "
 + org.kobjects.base64.Base64.encode("USER:PASS"
 .getBytes())));
 headerList.add(new HeaderProperty("Connection", "Keep-Alive"));
 androidHttpTransport.debug = true;
 androidHttpTransport.call(SOAP_ACTION, envelope, headerList);
 String answer = androidHttpTransport.responseDump;
 response = (SoapObject) envelope.bodyIn;
 }catch(Exception e){
 e.printStackTrace();
 }

 return response;
 }

}

3. And used it later in calls. A simple call is as follows.

SoapObject request = new SoapObject(
 "urn:sap-com:document:sap:soap:functions:mc-style",
 "ZbapiGetRoutes");
 request.addProperty("RoutesList", "");
SoapObject response;
response = SAPServiceCaller.webServiceCall(request, URL, false);
 SoapObject routesList = (SoapObject) response.getProperty("RoutesList");

This is a simple code that calls a method to return a list of Routes that one has to visit and read the meters in homes and upload them later to the backend.
With this simple “trick” I managed to build a Demo app for an SAP Backend with ISU Component that was displaying the Routes and devices from which the Meter data were read and uploaded to the backend for later billing purposes.

Hope it helps someone out there.

In the following posts I will try to keep up with the trends and write something about what I learned from the course that SAP was offered at openSAP  about Mobile development with the SAP Mobile Platform and maybe other ways how to achieve this as well.

Happy coding!

Advertisements

Material master creation for all organizational levels with BAPI_MATERIAL_SAVEDATA

I have to admit that I am lazy when it comes to creating test data before user trainings. Too much clicking for nothing. I’d rather stay overnight and prepare LSMW tool, eCATT tool or write some ABAP code that would either generate data or simply upload some Excel data then click it myself. This is what I’d do even if I have to add manually 3 test cases. Another good thing is that in the process of creating the tool you learn a lot about the system.

This time the problem is different, there was a need to create material master data for all organizational levels… And there were lots of them! Imagine 9 company codes, each with 1-3 plants, each plant with 1-5,6,7-13, 14, 15 or more storage locations, multiply these and you’ll see why I say “lazy”… Then there was the WM part with the warehouses, storage types and storage bins and all the materials could be placed everywhere! And the PM team asked me to do this every other week so that they could test some different scenarios… So I ABAPed my way out of it 😉

The point is the following code is just to show you that sometimes ABAPing is better than all the other tools! And with BAPI_MATERIAL_SAVEDATA you can do it all at once and you have a better insight of what is happening than with LSMW (OK.. this is just my opinion).

So let’s see what this code is all about.

First check the FM through se37 -> BAPI_MATERIAL_SAVEDATA and see which tables/structures you need. If you don’t really understand which you should use consult Google or even better consult a senior and you will realize which of them you really need. HINT: Another starting point how you can discover which of the structures you need to use is to create manually one new material in the system and see which are obligatory fields while you create it and through the technical information find out the structure where they belong. So in an hour 0r two you will (like it or not) learn all the tables that make the material master data.

REPORT  ZMM_MMASTER_ALL.
DATA: HEADDATA LIKE  BAPIMATHEAD,
CLIENTDATA LIKE  BAPI_MARA,
CLIENTDATAX LIKE  BAPI_MARAX,
PLANTDATA LIKE  BAPI_MARC,
PLANTDATAX LIKE  BAPI_MARCX,
PLANNINGDATA LIKE  BAPI_MPGD,
PLANNINGDATAX LIKE  BAPI_MPGDX,
STORAGELOCATIONDATA LIKE  BAPI_MARD,
STORAGELOCATIONDATAX LIKE  BAPI_MARDX,
VALUATIONDATA LIKE  BAPI_MBEW,
VALUATIONDATAX LIKE  BAPI_MBEWX,
WAREHOUSENUMBERDATA LIKE  BAPI_MLGN,
WAREHOUSENUMBERDATAX LIKE  BAPI_MLGNX,
SALESDATA LIKE  BAPI_MVKE,
SALESDATAX LIKE  BAPI_MVKEX,
STORAGETYPEDATA LIKE  BAPI_MLGT,
STORAGETYPEDATAX LIKE  BAPI_MLGTX,
FLAG_ONLINE LIKE  BAPIE1GLOBAL_DATA-TESTRUN,
RETURN LIKE  BAPIRET2,
S_LOC TYPE T001L-LGORT,
ST_TYPE TYPE BAPI_MLGT-STGE_TYPE,
SLS_ORG TYPE BAPI_MVKE-SALES_ORG.
TABLES: BAPI_MAKT, BAPI_MLTX, BAPI_MLAN.
TABLES: T001L, T001W.
DATA: MATERIALDESCRIPTION TYPE TABLE OF BAPI_MAKT WITH HEADER LINE,
MATERIALLONGTEXT TYPE TABLE OF  BAPI_MLTX WITH HEADER LINE,
TAXCLASSIFICATIONS TYPE TABLE OF  BAPI_MLAN WITH HEADER LINE,
IT_T001L TYPE TABLE OF T001L WITH HEADER LINE,
IT_T001W TYPE TABLE OF T001W WITH HEADER LINE.

I am sure that by analyzing these lines of code you will learn many of the tables where the material master data is stored in SAP and the people there were nice enough to give them descriptive names in the BAPI so that you find your way out easily.

Next step internal table  that represents the migration structure and will be needed in the Excel file.

* --- INTERNAL TABLE
data:begin of itab occurs 0,
MATERIAL type BAPIMATHEAD-MATERIAL,
IND_SECTOR type c,
MATL_TYPE type bapimathead-MATL_TYPE,
OLD_MAT_NO type bapi_mara-old_mat_no,
EXTMATLGRP TYPE BAPI_MARA-EXTMATLGRP,
MATL_GROUP type bapi_mara-MATL_GROUP,
MATL_DESC TYPE BAPI_MAKT-MATL_DESC,
MATL_DESCMK TYPE BAPI_MAKT-MATL_DESC,
BASE_UOM TYPE BAPI_MARA-BASE_UOM,
TRANS_GRP TYPE BAPI_MARA-TRANS_GRP,
BATCH_MGMT TYPE BAPI_MARA-BATCH_MGMT,
LOADINGGRP TYPE BAPI_MARC-LOADINGGRP,
AVAILCHECK TYPE BAPI_MARC-AVAILCHECK,
SERNO_PROF TYPE BAPI_MARC-SERNO_PROF,
MRP_TYPE TYPE BAPI_MARC-MRP_TYPE,
LOTSIZEKEY TYPE BAPI_MARC-LOTSIZEKEY,
MRP_CTRLER TYPE BAPI_MARC-MRP_CTRLER,
SM_KEY TYPE BAPI_MARC-SM_KEY,
PLND_DELRY TYPE BAPI_MARC-PLND_DELRY,
ACCT_ASSGT TYPE BAPI_MVKE-ACCT_ASSGT,
ITEM_CAT TYPE BAPI_MVKE-ITEM_CAT,
PLACEMENT TYPE BAPI_MLGN-PLACEMENT,
WITHDRAWAL TYPE BAPI_MLGN-WITHDRAWAL,
*     STGE_TYPE TYPE BAPI_MLGT-STGE_TYPE,
STGE_BIN TYPE BAPI_MLGT-STGE_BIN,
PRICE_CTRL TYPE BAPI_MBEW-PRICE_CTRL,
VAL_CLASS TYPE BAPI_MBEW-VAL_CLASS,
STD_PRICE TYPE BAPI_MBEW-STD_PRICE,
end of itab,
IT_BAPI LIKE BAPI_MAKT OCCURS 0 WITH HEADER LINE.

This table should be prepared so that it suits your needs (or your client’s needs). And following is the code for uploading the file in the table.

AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_file.
PERFORM GET_FILE.
START-OF-SELECTION.
  PERFORM UPLOAD_FILE_ITAB.
PERFORM CALL_BAPI.
*&---------------------------------------------------------------------*
*& Form GET_FILE
*&---------------------------------------------------------------------*
FORM GET_FILE.
CALL FUNCTION 'F4_FILENAME'
EXPORTING
program_name  = syst-cprog
dynpro_number = syst-dynnr
IMPORTING
file_name     = p_file.
ENDFORM.   " GET_FILE
*&---------------------------------------------------------------------*
*& Form UPLOAD_FILE_ITAB
*&---------------------------------------------------------------------*
FORM UPLOAD_FILE_ITAB .
DATA:v_file TYPE string.
MOVE p_file TO v_file. CALL FUNCTION 'GUI_UPLOAD'
EXPORTING
filename = v_file
filetype = 'ASC'
has_field_separator = 'X'
TABLES
data_tab = itab .
ENDFORM. "UPLOAD_FILE_ITAB

OK let’s get to the most serious part of this migrating code. I hope you already analyzed the BAPI and realized that for every BAPI structure there is a BAPIX 🙂 These contain the same fields like the BAPI but they are all single chars and if they have the value ‘X’ it means update should be done with the value given in the BAPI structure, if it is space update shouldn’t be done. So let’s see the core of the code…

*&---------------------------------------------------------------------*
*& Form CALL_BAPI
*&---------------------------------------------------------------------*
form CALL_BAPI .
  LOOP AT ITAB.
*CODE FOR CREATION
    SELECT *
FROM T001W
INTO IT_T001W
WHERE BEDPL = 'X'.
APPEND IT_T001W.
ENDSELECT.
*LOOP PLANTS / SORgs(Plant(2)00) / WHouses (Plant(3)) / StorageType(SLoc(1)0SLoc(4)
LOOP AT IT_T001W.
CLEAR IT_T001L[].
SELECT *
FROM T001L
INTO IT_T001L
WHERE T001L~WERKS = IT_T001W-WERKS
AND T001L~XBLGO = 'X'.
APPEND IT_T001L.
ENDSELECT.
LOOP AT IT_T001L.
*LOOP SLocs / Storage type
        CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT'
EXPORTING
INPUT         = ITAB-MATERIAL
IMPORTING
OUTPUT        = HEADDATA-MATERIAL
.
       " HEADDATA-MATERIAL  = ITAB-MATERIAL. " matnr
HEADDATA-IND_SECTOR = ITAB-IND_SECTOR. " indsector
HEADDATA-MATL_TYPE = ITAB-MATL_TYPE. " mattype
HEADDATA-BASIC_VIEW = 'X'.
HEADDATA-SALES_VIEW  = 'X'.
HEADDATA-PURCHASE_VIEW  = 'X'.
HEADDATA-MRP_VIEW  = 'X'.
HEADDATA-STORAGE_VIEW  = 'X'.
HEADDATA-WAREHOUSE_VIEW  = 'X'.
HEADDATA-ACCOUNT_VIEW  = 'X'.
*CLIENT DATA + UPDATE FIELD
        CLIENTDATA-MATL_GROUP = ITAB-MATL_GROUP. "matgr
CLIENTDATA-BASE_UOM = ITAB-BASE_UOM. "unitmeasure
CLIENTDATA-OLD_MAT_NO = ITAB-OLD_MAT_NO.
CLIENTDATA-EXTMATLGRP = ITAB-EXTMATLGRP.
CLIENTDATA-TRANS_GRP = ITAB-TRANS_GRP.
CLIENTDATA-BATCH_MGMT = ITAB-BATCH_MGMT.
        CLIENTDATAX-MATL_GROUP = 'X'. "ITAB-MATL_GROUP'. "matgr
CLIENTDATAX-BASE_UOM = 'X'. " ITAB-BASE_UOM. "unitmeasure
CLIENTDATAX-OLD_MAT_NO = 'X'. "ITAB-OLD_MAT_NO.
CLIENTDATAX-EXTMATLGRP = 'X'. "ITAB-EXTMATLGRP.
CLIENTDATAX-TRANS_GRP = 'X'. "ITAB-TRANS_GRP.
CLIENTDATAX-BATCH_MGMT = 'X'. "ITAB-BATCH_MGMT.
        PLANTDATA-PLANT = IT_T001W-WERKS.
PLANTDATA-PUR_GROUP = '999'.
PLANTDATA-AVAILCHECK = ITAB-AVAILCHECK.
PLANTDATA-MRP_TYPE = ITAB-MRP_TYPE.
PLANTDATA-LOTSIZEKEY = ITAB-LOTSIZEKEY.
PLANTDATA-MRP_CTRLER = ITAB-MRP_CTRLER.
PLANTDATA-SM_KEY = ITAB-SM_KEY.
PLANTDATA-PLND_DELRY = ITAB-PLND_DELRY.
PLANTDATA-LOADINGGRP = ITAB-LOADINGGRP.
PLANTDATA-SERNO_PROF = ITAB-SERNO_PROF.
        PLANTDATAX-PLANT = IT_T001W-WERKS.
PLANTDATAX-PUR_GROUP = 'X'.
PLANTDATAX-AVAILCHECK = 'X'.
PLANTDATAX-MRP_TYPE = 'X'.
PLANTDATAX-LOTSIZEKEY = 'X'.
PLANTDATAX-MRP_CTRLER = 'X'.
PLANTDATAX-SM_KEY = 'X'.
PLANTDATAX-PLND_DELRY = 'X'.
PLANTDATAX-LOADINGGRP = 'X'.
PLANTDATA-SERNO_PROF = 'X'.
*
*PLANNINGDATA-PLANT = '1100'.
*PLANNINGDATA-PLNG_MATL = ''.
*PLANNINGDATA-PLNG_PLANT  = ''.
*
*PLANNINGDATAX-PLANT = 'X'.
*PLANNINGDATAX-PLNG_MATL = 'X'.
*PLANNINGDATAX-PLNG_PLANT  = 'X'.
        STORAGELOCATIONDATA-PLANT = IT_T001W-WERKS.
STORAGELOCATIONDATA-STGE_LOC = IT_T001L-LGORT.
STORAGELOCATIONDATA-STGE_BIN = ITAB-STGE_BIN.
        STORAGELOCATIONDATAX-PLANT = IT_T001W-WERKS.
STORAGELOCATIONDATAX-STGE_LOC = IT_T001L-LGORT.
STORAGELOCATIONDATAX-STGE_BIN = 'X'.
        VALUATIONDATA-VAL_AREA = IT_T001W-WERKS.
VALUATIONDATA-PRICE_CTRL = ITAB-PRICE_CTRL.
VALUATIONDATA-VAL_CLASS = ITAB-VAL_CLASS.
VALUATIONDATA-STD_PRICE = ITAB-STD_PRICE.
        VALUATIONDATAX-VAL_AREA = IT_T001W-WERKS.
VALUATIONDATAX-PRICE_CTRL = 'X'.
VALUATIONDATAX-VAL_CLASS = 'X'.
VALUATIONDATAX-STD_PRICE = 'X'.
*WAREHOUSENUMBERDATA
        WAREHOUSENUMBERDATA-WHSE_NO = IT_T001W-WERKS(3).
WAREHOUSENUMBERDATA-PLACEMENT = ITAB-PLACEMENT.
WAREHOUSENUMBERDATA-WITHDRAWAL = ITAB-WITHDRAWAL.
        WAREHOUSENUMBERDATAX-WHSE_NO = IT_T001W-WERKS(3).
WAREHOUSENUMBERDATAX-PLACEMENT = 'X'.
WAREHOUSENUMBERDATAX-WITHDRAWAL = 'X'.
*STORAGETYPEDATA
        CONCATENATE IT_T001L-LGORT(2) IT_T001L-LGORT+3(1) INTO ST_TYPE.
STORAGETYPEDATA-WHSE_NO = IT_T001W-WERKS(3).
STORAGETYPEDATA-STGE_TYPE = ST_TYPE.
STORAGETYPEDATA-STGE_BIN = ITAB-STGE_BIN.
        STORAGETYPEDATAX-WHSE_NO = IT_T001W-WERKS(3).
STORAGETYPEDATAX-STGE_TYPE = ST_TYPE.
STORAGETYPEDATAX-STGE_BIN = 'X'.
*SALESDATA
        CONCATENATE IT_T001W-WERKS(2) '00' INTO SLS_ORG.
SALESDATA-SALES_ORG = SLS_ORG.
SALESDATA-DISTR_CHAN = '99'.
SALESDATA-ITEM_CAT = ITAB-ITEM_CAT.
SALESDATA-ACCT_ASSGT = ITAB-ACCT_ASSGT.
        SALESDATAX-SALES_ORG = SLS_ORG.
SALESDATAX-DISTR_CHAN = '99'.
SALESDATAX-ITEM_CAT = 'X'.
SALESDATAX-ACCT_ASSGT = 'X'.
* MATERIAL DESCRIPTION - MK & EN
MATERIALDESCRIPTION-LANGU = '봋'.
MATERIALDESCRIPTION-LANGU_ISO  = 'MK'.
MATERIALDESCRIPTION-MATL_DESC = ITAB-MATL_DESCMK.
APPEND MATERIALDESCRIPTION.
        MATERIALDESCRIPTION-LANGU = 'E'.
MATERIALDESCRIPTION-LANGU_ISO  = 'EN'.
MATERIALDESCRIPTION-MATL_DESC = ITAB-MATL_DESC.
APPEND MATERIALDESCRIPTION.
*TAXCLASSIFICATIONS
*MK Macedonia MWST  Output Tax  1 Taxable
TAXCLASSIFICATIONS-DEPCOUNTRY = 'MK'.
TAXCLASSIFICATIONS-DEPCOUNTRY_ISO  ='MK'.
TAXCLASSIFICATIONS-TAX_TYPE_1  = 'MWST'.
TAXCLASSIFICATIONS-TAXCLASS_1  = '1'.
APPEND TAXCLASSIFICATIONS.
        CALL FUNCTION 'BAPI_MATERIAL_SAVEDATA'
EXPORTING
HEADDATA                   = HEADDATA
CLIENTDATA                 = CLIENTDATA
CLIENTDATAX                = CLIENTDATAX
PLANTDATA                  = PLANTDATA
PLANTDATAX                 = PLANTDATAX
*   FORECASTPARAMETERS         =
*   FORECASTPARAMETERSX        =
*    PLANNINGDATA               = PLANNINGDATA
*    PLANNINGDATAX              = PLANNINGDATAX
STORAGELOCATIONDATA        = STORAGELOCATIONDATA
STORAGELOCATIONDATAX       = STORAGELOCATIONDATAX
VALUATIONDATA              = VALUATIONDATA
VALUATIONDATAX             = VALUATIONDATAX
WAREHOUSENUMBERDATA        = WAREHOUSENUMBERDATA
WAREHOUSENUMBERDATAX       = WAREHOUSENUMBERDATAX
SALESDATA                  = SALESDATA
SALESDATAX                 = SALESDATAX
STORAGETYPEDATA            = STORAGETYPEDATA
STORAGETYPEDATAX           = STORAGETYPEDATAX
*   FLAG_ONLINE                = ' '
*   FLAG_CAD_CALL              = ' '
*   NO_DEQUEUE                 = ' '
*   NO_ROLLBACK_WORK           = ' '
IMPORTING
RETURN                     = RETURN
TABLES
MATERIALDESCRIPTION        = MATERIALDESCRIPTION
*   UNITSOFMEASURE             =
*   UNITSOFMEASUREX            =
*   INTERNATIONALARTNOS        =
*    MATERIALLONGTEXT           = MATERIALLONGTEXT
TAXCLASSIFICATIONS         = TAXCLASSIFICATIONS
*   RETURNMESSAGES             =
*   PRTDATA                    =
*   PRTDATAX                   =
*   EXTENSIONIN                =
*   EXTENSIONINX               =
.
        CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' .
write: 'PLANT: ',IT_T001W-WERKS, ' STORAGE LOCATION: ', IT_T001L-LGORT, ' ',  RETURN-type , ' MESSAGE:', RETURN-MESSAGE, '/'.
WAIT UP TO 2 SECONDS.
ENDLOOP.
ENDLOOP.
ENDLOOP. " itab
ENDFORM.                    "call_bapi

Run the code, go make some coffee and check the results after it is done 😉

ABAP, XML, XSLT and something about acquisition of web services in SAP

Long time no written I know… The last couple of months were a bit busy so I guess I owe my blog some more attention… Just noticed that there are maybe three draft posts waiting for approval:  one for the Digra conference that I visited in September (more on: DIGRA2011  and I was a speaker there about the games that I have made for motivating physical activity: Me,  but the folks were even cooler than I could imagine so they really deserve a post), one kind of a review of my new smartphone HTC Desire S (the device: HTC Desire S) and one about ABAP & JSON project that will be the only one that will see the the light of the day as soon as I finish it 😀

I promise this 2012 I will write more often and I guess someone will find what they need in the posts.

As a first 2012 post I  chose to write something about SAP and web service acquisition. This is a really simple example with simple code but it can be further extended and improved in different cases.

I would suppose that the reader is familiar with XML and XSLT (if not please check the following basic tutorials: W3Schools ) and has experience in any programming language and thus will easily “read” and understand the ABAP lines of code. Maybe he/she has also tried some web service acquisition in some other language but is an ABAP beginner and wants to get some job done really quickly.

So.. we have some web-service  from the National Bank of Republic of Macedoniacurrency download – for getting the currency exchange rates on a given date. This is one of the main things that is implemented in any project since any company needs the exchange rates in many of the processes. Since I am not here to write about business processes I will stick to the technical details.

So what one has to do first is to see what are the input parameters and what is the result from the web service. Let’s take the method GetExchangeRates as an example the input parameters are the start and end date for which we need the rates and the result is a string. When the answer is encapsulated in a string it requires some testing to get the full information about the response. I use soapUI to test the methods and analyze the answers and for this method I got something like:

<GetExchangeRateResult><![CDATA[<dsKurs>
  <xs:schema id="dsKurs" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="dsKurs" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
      <xs:complexType>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
          <xs:element name="KursZbir">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="RBr" type="xs:double" minOccurs="0" />
                <xs:element name="Datum" type="xs:dateTime" minOccurs="0" />
                <xs:element name="Valuta" type="xs:double" minOccurs="0" />
                <xs:element name="Oznaka" type="xs:string" minOccurs="0" />
                <xs:element name="Drzava" type="xs:string" minOccurs="0" />
                <xs:element name="Nomin" type="xs:double" minOccurs="0" />
                <xs:element name="Sreden" type="xs:double" minOccurs="0" />
                <xs:element name="DrzavaAng" type="xs:string" minOccurs="0" />
                <xs:element name="NazivMak" type="xs:string" minOccurs="0" />
                <xs:element name="NazivAng" type="xs:string" minOccurs="0" />
                <xs:element name="Datum_f" type="xs:dateTime" minOccurs="0" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
        </xs:choice>
      </xs:complexType>
    </xs:element>
  </xs:schema>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>978</Valuta>
    <Oznaka>EUR</Oznaka>
    <Drzava>ЕМУ</Drzava>
    <Nomin>1</Nomin>
    <Sreden>61.5044</Sreden>
    <DrzavaAng>EMU</DrzavaAng>
    <NazivMak>евро</NazivMak>
    <NazivAng>Euro</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>840</Valuta>
    <Oznaka>USD</Oznaka>
    <Drzava>С А Д</Drzava>
    <Nomin>1</Nomin>
    <Sreden>48.2918</Sreden>
    <DrzavaAng>USA</DrzavaAng>
    <NazivMak>САД долар</NazivMak>
    <NazivAng>US dollar</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>826</Valuta>
    <Oznaka>GBP</Oznaka>
    <Drzava>В.Британија</Drzava>
    <Nomin>1</Nomin>
    <Sreden>74.0526</Sreden>
    <DrzavaAng>U.K.</DrzavaAng>
    <NazivMak>британска фунта</NazivMak>
    <NazivAng>British pound</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>756</Valuta>
    <Oznaka>CHF</Oznaka>
    <Drzava>Швајцарија</Drzava>
    <Nomin>1</Nomin>
    <Sreden>50.7797</Sreden>
    <DrzavaAng>Switzerland</DrzavaAng>
    <NazivMak>швајцарски франк</NazivMak>
    <NazivAng>Swiss franc</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>752</Valuta>
    <Oznaka>SEK</Oznaka>
    <Drzava>Шведска</Drzava>
    <Nomin>1</Nomin>
    <Sreden>6.9485</Sreden>
    <DrzavaAng>Sweden</DrzavaAng>
    <NazivMak>шведска круна</NazivMak>
    <NazivAng>Swedish krona</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>578</Valuta>
    <Oznaka>NOK</Oznaka>
    <Drzava>Норвешка</Drzava>
    <Nomin>1</Nomin>
    <Sreden>8.0115</Sreden>
    <DrzavaAng>Norway</DrzavaAng>
    <NazivMak>норвешка круна</NazivMak>
    <NazivAng>Norwegian krone</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>392</Valuta>
    <Oznaka>JPY</Oznaka>
    <Drzava>Јапонија</Drzava>
    <Nomin>100</Nomin>
    <Sreden>62.8109</Sreden>
    <DrzavaAng>Japan</DrzavaAng>
    <NazivMak>јапонски јен</NazivMak>
    <NazivAng>Japanese yen</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>208</Valuta>
    <Oznaka>DKK</Oznaka>
    <Drzava>Данска</Drzava>
    <Nomin>1</Nomin>
    <Sreden>8.2705</Sreden>
    <DrzavaAng>Denmark</DrzavaAng>
    <NazivMak>данска круна</NazivMak>
    <NazivAng>Danish krone</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>124</Valuta>
    <Oznaka>CAD</Oznaka>
    <Drzava>Канада</Drzava>
    <Nomin>1</Nomin>
    <Sreden>47.5084</Sreden>
    <DrzavaAng>Canada</DrzavaAng>
    <NazivMak>канадски долар</NazivMak>
    <NazivAng>Canadian dollar</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>36</Valuta>
    <Oznaka>AUD</Oznaka>
    <Drzava>Австралија</Drzava>
    <Nomin>1</Nomin>
    <Sreden>49.9914</Sreden>
    <DrzavaAng>Australia</DrzavaAng>
    <NazivMak>австралиски долар</NazivMak>
    <NazivAng>Australian dollar</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>

Aha there is the definition: KursZbir is the element containing all the data about the exchange rate of the currency according the Macedonian denar. And that is the content we need to put in an ABAP table and do what is necessary with it.

So before you think about which parsing technique should be used and all that I will tell you that the solution is just couple of clicks away. SAP is really sometimes nice to developers and sometimes it is better to search for a day or two first for some technique or a module instead of writing a bunch of code.

Next please go through the following tutorial about how to set up a proxy and test it on the following link. It contains detailed explanation with screens how to create a Proxy class, set up URL and create valid SOAP message and all with couple of clicks no line of code. 🙂 This is not the only one available on the internet you can find many more…

You see here that the method is really simple and the result is just square root of the value sent. The problem is when there is a more complex structure like the one we have to deal with now. Now we have to make a transformation. It is nothing too complicated just a way to tell one really great SAP function how to transform the “weird” XML data string into table data. It is done through the Object Navigator (SE80) . Open the menu: Workbench -> Edit Object. From the dialog select transformation and then create.

For this example the following XSL transformation was used: (i put it here so that you can easily copy and paste )

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sap="http://www.sap.com/sapxsl" version="1.0">
  <xsl:strip-space elements="*"/>
  <xsl:template match="/">
    <asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
      <asx:values>
        <ALL_KURS>
          <xsl:apply-templates select="//KursZbir"/>
        </ALL_KURS>
      </asx:values>
    </asx:abap>
  </xsl:template>
  <xsl:template match="KursZbir">
  <ALLKURS>
    <RBR>
      <xsl:value-of select="RBr"/>
    </RBR>
    <DATUM>
      <xsl:value-of select="Datum"/>
    </DATUM>
    <VALUTA>
      <xsl:value-of select="Valuta"/>
    </VALUTA>
    <OZNAKA>
      <xsl:value-of select="Oznaka"/>
    </OZNAKA>
    <DRZAVA>
      <xsl:value-of select="Drzava"/>
    </DRZAVA>
    <NOMIN>
      <xsl:value-of select="Nomin"/>
    </NOMIN>
    <SREDEN>
      <xsl:value-of select="Sreden"/>
    </SREDEN>
    <DRZAVAANG>
      <xsl:value-of select="DrzavaAng"/>
    </DRZAVAANG>
    <NAZIVMAK>
      <xsl:value-of select="NazivMak"/>
    </NAZIVMAK>
    <NAZIVANG>
      <xsl:value-of select="NazivAng"/>
    </NAZIVANG>
    <DATUM_F>
      <xsl:value-of select="Datum_f"/>
    </DATUM_F>
 </ALLKURS>
  </xsl:template>
</xsl:transform>

Actually writing the transformation is the hard part. The ABAP code will actually be short and simple.

Here it goes:

*&---------------------------------------------------------------------*
*& Report  Z_KURS_DOWNLOAD
*&---------------------------------------------------------------------*
 REPORT  z_kurs_download.
 DATA: proxy TYPE REF TO zkurstestco_kurs_soap .
 DATA : st_data TYPE start_date ,
        en_data TYPE end_date.
 data: cr_date type string.
 concatenate sy-datum+6(2) '.' sy-datum+4(2) '.' sy-datum(4) into cr_date.

 DATA: zinput TYPE zkurstestget_exchange_rate_so1,
       zoutput TYPE zkurstestget_exchange_rate_soa.
 DATA: result TYPE string.

 TYPES : BEGIN OF kurszbir,
       rbr TYPE string,
       datum TYPE string,
       valuta TYPE string,
       oznaka TYPE string,
       drzava TYPE string,
       nomin TYPE string,
       sreden TYPE string,
       drzavaang TYPE string,
       nazivmak TYPE string,
       nazivang TYPE string,
       datum_f TYPE string ,
 END OF kurszbir.

 DATA itab TYPE kurszbir OCCURS 0.
 DATA lt_kurs TYPE TABLE OF kurszbir.
 DATA wa_kurs TYPE TABLE OF kurszbir WITH HEADER LINE.

 TRY.
     CREATE OBJECT proxy
       EXPORTING
         logical_port_name = 'ZKURSTESTCO_KURS_SOAP_PORT'.
   CATCH cx_ai_system_fault .
 ENDTRY.

 * ADD SYSTEM DATE IN FORMAT DD.MM.YYYY
 zinput-start_date = cr_date.
 zinput-end_date = cr_date. "cr_date .

 TRY.
     CALL METHOD proxy->get_exchange_rate
       EXPORTING
         input  = zinput
       IMPORTING
         output = zoutput.
   CATCH cx_ai_system_fault .
   CATCH cx_ai_application_fault .
 ENDTRY.

 result = zoutput-GET_EXCHANGE_RATE_RESULT.
 REPLACE ALL OCCURRENCES OF '##' IN result WITH ' ' .

 TRY.
     CALL TRANSFORMATION Z_KURS_XSLT
             SOURCE XML result
             RESULT     all_kurs = lt_kurs.
   CATCH cx_ai_system_fault.
 ENDTRY.

 MOVE lt_kurs TO wa_kurs[]. 

 LOOP AT wa_kurs .
   write: / wa_kurs-datum, ' ', wa_kurs-valuta, ' ',
  wa_kurs-nomin, ' ', wa_kurs-sreden, ' ', wa_kurs-drzavaang, ' ',    wa_kurs-nazivmak, ' '.
 ENDLOOP.

The output of the program is the following:

After that you can either schedule a job or set a transaction and the client can perform it on their own.

I guess someone will find this example useful and the good thing is that it is actually possible to test it because banks are generally reliable for this kind of data 🙂

Next time maybe something more about more complex XML structures.

Multi-language messages in SAP

The title of this post says it all.  It is known that usually international companies can afford to implement SAP and that part of the correspondence that they will have will be in several languages. So here is a simple procedure that can be used when a message to customers from different countries should be sent.

Step 1 -> Open the transaction s010 for Standard text creation

Step 2 ->  In the field add descriptive

TEXT NAME – (in this case – Z_CUST_MSG)

TEXD ID – ST

LANGUAGE – any.

Step 3 ->  Write the content of the message in the text editor and save it.

Step 4 ->  Repeat Steps 1-3 for all the languages that you would like to use.

Step 4 -> Using the text via ABAP code.

* t_lines to hold the text
  data: t_lines type table of tline with header line.
* CUSTOMER COMMUNICATION LANGUAGE
  select spras
    into cust_lang
    from kna1
    where kunnr = p_kunnr.
  endselect.
* GET THE TEXT FOR THE BODY OF THE MESSAGE
  CALL FUNCTION 'READ_TEXT'
    EXPORTING
      CLIENT   = SY-MANDT
      ID       = 'ST'
      LANGUAGE = cust_lang
      NAME     = 'Z_CUST_MSG'
      OBJECT   = 'TEXT'
    TABLES
      LINES    = t_lines.
  IF SY-SUBRC <> 0.
* MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO
*         WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.
* IF THERE IS NO TRANSLATION OF THE LANGUAGE ASK USER
  ENDIF.
  DATA: counter type i.
 counter = 0.
 loop at t_lines.
 if counter = 0.
* in this case the first line of the text is used as a subject text for the message
      REPLACE ALL OCCURRENCES OF '/' IN t_lines WITH ''.
      modify t_lines.
      msg_sub = t_lines.
      counter = counter + 1.
    else.
      REPLACE ALL OCCURRENCES OF '/' IN t_lines WITH ''.
      modify t_lines.
      concatenate msg_body t_lines CL_ABAP_CHAR_UTILITIES=>NEWLINE into msg_body.
      counter = counter + 1.
    endif.
  endloop.

Outlook messages from SAP system

This is a sample code that can be used to create draft messages in your Outlook through SAP. Of course it is important to understand what it does so I tried to comment as much I can.

The VB code that can be used to create draft message is given here:

Sub create_draftmsg()
  Dim oApp As Outlook.Application
    Dim oEmail As Outlook.MailItem
    Set oApp = New Outlook.Application
    Set oEmail = oApp.CreateItem(olMailItem)
With oEmail
        .To = "to@mail.com"
        .CC = ""
        .Subject = "SUBJECT"
        .Body = "BODY TEXT"
        .Attachments.Add "C:\Cat.bmp", olByValue
        .Save
    End With
    Set oEmail = Nothing
End Sub

The ABAP code is given below.

INCLUDE OLE2INCL.
* they are all OLE2_OBJECTS so it is important to know the real classes
DATA: OUTLOOK TYPE OLE2_OBJECT,
MAILITEM TYPE OLE2_OBJECT,
ATTACHMENTS TYPE OLE2_OBJECT,
APPT TYPE OLE2_OBJECT,
DESTI TYPE OLE2_OBJECT,
BODYMAIL(600) TYPE C.
CREATE OBJECT OUTLOOK 'OUTLOOK.APPLICATION'.
* Get the outlook namespace
CALL METHOD OF OUTLOOK 'GetNamespace' = NAMESPACE
EXPORTING
#1 = 'MAPI'.
* Get outlook folders and create a reference to sent mail folder
* (myfolder)
* If this is not used than a draft message is not created when Outlook is not running
CALL METHOD OF NAMESPACE 'GetDefaultFolder' = MYFOLDER  EXPORTING     #1 = 16.    "olFolderDraftMail (value from a short debugging in VB)
CALL METHOD OF OUTLOOK 'CreateItem' = OUTMAIL EXPORTING #1 = 0. " 0 - MailItem
SET PROPERTY OF OUTMAIL 'SUBJECT' = 'SUBJECT'.
SET PROPERTY OF OUTMAIL 'BODY' = BODYMAIL.
CALL METHOD OF OUTMAIL 'RECIPIENTS' = DESTI.
CALL METHOD OF DESTI 'ADD' EXPORTING #1 = 'recipient@mail.com'.
CALL METHOD OF OUTMAIL 'ATTACHMENTS' = ATTS.
CALL METHOD OF ATTS 'ADD' EXPORTING #1 = 'filepath:\file.type'.
CALL METHOD OF OUTMAIL 'SAVE'.
FREE OBJECT DESTI.
FREE OBJECT ATTS.
FREE OBJECT OUTMAIL.
FREE OBJECT OUTLOOK.

Today’s wisdom: Use Macro recorder to discover the classes needed in order to use Microsoft goodies in ABAP! 🙂

SAP Technical sites/blogs I often consult…

The best technical  ones

http://www.saptechnical.com

http://abap4.tripod.com

http://abaplovers.blogspot.com

http://sap.ittoolbox.com

http://www.erpdb.info

http://www.freesaptutorial.com/

http://www.erpgenie.com

I’ll update this list every time I find a site/blog that will help me.

Browsing SAP workplace messages via Android phone

If I want to claim that I am an SAP enthusiast I have to prove that somehow…

The video below is a demo app that I submitted at the SAP TECHED 2010 contest. It is really more of a test on the capabilities that some tools offer like the good and old SAP .NET connector ( I’d prefer using the SAP Java connector but this was what I had at the moment of making the app) and the lightweight SOAP library for Android phones – ksoap-2. I got 2-in-1 with this app- learned a lot about connecting SAP with external systems and started my Android development journey.

Hopefully I’ll make something more useful for this year’s SAP TECHED.  🙂

What the app does and how it does it is done in the video. It is subtitled because I had some difficulties with the sound.

It is a bit slow but if you are patient enough you’ll see the steps needed to get it done. I might add some parts of the code in future posts or if you really need it mail me and I’ll send it to you.

Since there was a lot of SAP wisdom in this post I am free for a whole week 🙂