Quantcast
Channel: SCN : Blog List - ABAP Connectivity
Viewing all 68 articles
Browse latest View live

Real-time notifications and workflow using ABAP Push Channels (websockets) Part 1: Creating the APC and AMC

$
0
0

After hearing all about websocket technology at Tech-ed last year, I’ve been extremely eager to try it out. I found there were a few great blogs already written about ABAP Push Channels and how to create your own. I recommend reading the following useful blogs and documents to get a good understanding of ABAP Push Channels:

 


and the following series of blogs by Masoud Aghadavoodi Jolfaei

 

 

So in this blog I'm not going to go into detail about what ABAP Push Channels are all about, I'd recommend reading the blogs and documents above for that.
I’m rather going to demonstrate a real-life possible use case for ABAP Push Channels (websockets).  As entertaining as it is seeing it being used for ping pong games, thats unlikely to be very useful in an enterprise environment.

 

After writing this, I realised that it would be way too large to post as 1 blog. So I have divided it into several parts and each part will be posted in the appropriate space on SCN. See links below:

 


Please Note that this was all done on an ABAP 740 SP06 backend. Much of this is really new and may not be available or the same in prior versions.

 

The idea is to build a small UI5 or Web Dynpro ABAP Application to display the users Workflow Inbox. This application can either be run standalone or could even be used as a Side Panel Application in the NWBC client, which would possibly be the most appropriate use since it will always be open and be in the users sight while they are busy with other transactions.

 

Although I am using workflow as the use case to demo this, truthfully, this can be applied to any application in a very similar manner. These notifications are not only being used to display a message in the application, but also to inform the application that there has been a change and a refresh is necessary, therefore the application can reload the data. A push to pull type scenario. This prevents the need for periodic refreshes or for the user to constantly be pressing the refresh button to see if there have been changes.

 

Step 1 – Create the ABAP Push Channel


Got transaction SAPC (ABAP Push Channels). Right click on “ABAP Push Channels” and click Create.

001.PNG

 

 

Enter the name, in this case ZAPC_WF_NOTIFY. Then save to transport or as local object.

002.PNG

 

 

Enter a description then press the Generate Class and Service button.

003.PNG

 

 

Once generation is complete you will see the following message:

004.PNG

 

 

Navigate into the new generated class ZCL_APC_WS_EXT_ZAPC_WF_NOTIFY. Select IF_APC_WS_EXTENSION~ON_START and press the Redefine button . Then do the same for IF_APC_WS_EXTENSION~ON_MESSAGE.

005.PNG

 

 

 

For now leave the implementations of these methods blank, as we will return to enter the code in here after creating the AMC (ABAP Messaging Channel). Activate the class. Then Save and Activate the ABAP Push Channel.

006.PNG

 

 

 

Step 2 – Create the ABAP Messaging Channel


Goto transaction SAMC and right click on “ABAP Messaging Channels” and press Create.

007.PNG

 

 

Enter the name ZAMC_WF_NOTIFY. Then save to transport or as local object.

008.PNG

 

 

Enter a Description. Put /workflow_notify in Channels and set Message Type ID to TEXT and Activity Scope to System (User will not allow messages to be sent from one user to another). In order to make the messages user specific, we use the channel extension in bind_message_amc_consumer method call and the create_message_producer calls.

 

Then add the following entry to list of Authorised Programs:

Authorised Program: ZCL_APC_WS_EXT_ZAPC_WF_NOTIFY (class was created in the previous  step)
Prog. Type: CLAS
Activity: Receive via APC WebSocket

 

In subsequent steps we will add in an additional entry in Authorised programs to allow the program broadcasting the message to use the ABAP Messaging Channel.

009.PNG

Save and Activate the AMC (ABAP Messaging Channel)

010.PNG

 

 

 

Step 3 - Implement the code for the Messaging Channel in the APC

 

Go back to the APC class we created in step 1 and now implement the following code in the IF_APC_WS_EXTENSION~ON_START method. We are going to leave the IF_APC_WS_EXTENSION~ON_MESSAGE method blank as in this use case the client won't be sending any messages back to the ABAP backend. However, if it were to do so, we would need to add code into this method to handle it.

 

METHOD if_apc_ws_extension~on_start .
     
DATA: lv_channel_ext TYPE amc_channel_extension_id.

     
TRY.
* Set the Channel extension to the user who is connecting,

* this is to ensure the user only receives messages for them   

      lv_channel_ext = sy-uname.

 

* Bind the APC (Websocket) we created in step 1 to the AMC ABAP Messagin Channel we created in step 2   

      DATA(lo_binding)= i_context->get_binding_manager( ).
      lo_binding->bind_amc_message_consumer( i_application_id  = 'ZAMC_WF_NOTIFY'

                                             i_channel_id      = '/workflow_notify'

                                             i_channel_extension_id = lv_channel_ext ).
     

     CATCH cx_apc_error INTO DATA(lx_apc_error).
          DATA(lv_message) = lx_apc_error->get_text( ).

          MESSAGE lx_apc_error->get_text( ) TYPE 'E'.
     ENDT
RY.

ENDMETHOD.

 

In the next part we will create the work item exits which will broadcast the message via the ABAP Messaging Channel.

 

See Real-time notifications and workflow using ABAP Push Channels (websockets) Part 2: Broadcasting the message via Workitem exits


Calling an external RESTful service from ABAP - HTTP Method GET

$
0
0

HTTP POST  method: http://scn.sap.com/community/abap/connectivity/blog/2014/11/09/calling-an-external-restful-service-from-abap--http-method-post


I have made inline code explanation. Please go through step by step description.


Scenario: GET operation to access: http://hostname.com:55400/vendavo/rest/agreements/XYZ


1) Create a RFC destination of type (HTTP Connections to External Server)


RFCDEST.JPG

content-type: application/json

 

DATA: lo_http_client     TYPE REF TO if_http_client,

         lo_rest_client     TYPE REF TO cl_rest_http_client,

         lv_url             TYPE        string,

         lv_body            TYPE        string,

         token              TYPE        string,

         agreements         TYPE        string,

         "Create a structure(or deep) that exactly matches your JSON response

         abap_response      TYPE        zca_serno_ln,

         lo_response    TYPE REF TO     if_rest_entity.

 

* Create HTTP intance using RFC restination created

* You can directly use the REST service URL as well

   cl_http_client=>create_by_destination(

    EXPORTING

      destination              = 'VENDAVO'    " Logical destination (specified in function call)

    IMPORTING

      client                   = lo_http_client    " HTTP Client Abstraction

    EXCEPTIONS

      argument_not_found       = 1

      destination_not_found    = 2

      destination_no_authority = 3

      plugin_not_active        = 4

      internal_error           = 5

      OTHERS                   = 6

  ).


* If you are using cl_http_client=>create_by_url use this code to supress and pass your

* HTTP basic authenication 

*  lo_http_client->propertytype_logon_popup = lo_http_client->co_disabled.

*  DATA l_username TYPE string.

*  DATA l_password TYPE string.

*  l_username = 'user'.

*  l_password = 'password'.

*  CALL METHOD lo_http_client->authenticate

*    EXPORTING

*      username = l_username

*      password = l_password.

 

* Create REST client instance

   CREATE OBJECT lo_rest_client

     EXPORTING

       io_http_client = lo_http_client.

 

* Set HTTP version

   lo_http_client->request->set_version( if_http_request=>co_protocol_version_1_0 ).

 

   IF lo_http_client IS BOUND AND lo_rest_client IS BOUND.

     DATA(id) = 'XYZ'.

     CONCATENATE 'agreements/' id INTO lv_url.

 

* Set the URI if any

     cl_http_utility=>set_request_uri(

       EXPORTING

         request = lo_http_client->request    " HTTP Framework (iHTTP) HTTP Request

         uri     = lv_url                     " URI String (in the Form of /path?query-string)

     ).

* Set request header if any

     CALL METHOD lo_rest_client->if_rest_client~set_request_header

       EXPORTING

         iv_name  = 'auth-token'

         iv_value = token.

* HTTP GET

     lo_rest_client->if_rest_client~get( ).

  

* HTTP response   

     lo_response = lo_rest_client->if_rest_client~get_response_entity( ).

  

* HTTP return status   

     DATA(http_status)   = lo_response->get_header_field( '~status_code' ).

  

* HTTP JSON return string   

     DATA(json_response) = lo_response->get_string_data( ).

 

* Class to convert the JSON to an ABAP sttructure

    DATA lr_json_deserializer TYPE REF TO cl_trex_json_deserializer.

    CREATE OBJECT lr_json_deserializer.

    lr_json_deserializer->deserialize( EXPORTING json = json_response IMPORTING abap = abap_response ).

Calling an external RESTful service from ABAP - HTTP Method POST

$
0
0

HTTP GET Method: Calling an external RESTful service from ABAP - HTTP Method GET


POST operation is almost equal to GET operation as my previous blog. Please refer my previous blog for more inline code description.

In POST operation we need to set a payload (JSON or XML) during the operation.



Create a RFC destination of type (HTTP Connections to External Server)


RFCDEST.JPG


Scenario

HTTP POST URL: http://hostname.com:55400/vendavo/rest/salesorder with Header Authentication token


Request JSON: '{ "salesorder":"25252525", "type":"Direct"}'


Expected Response JSON: '{ "token":"CONFIRM7391797"}'


Code:


DATA: lo_http_client     TYPE REF TO if_http_client,

         lo_rest_client     TYPE REF TO cl_rest_http_client,

         lv_url             TYPE        string,

         http_status        TYPE        string,

         lv_body            TYPE        string.

 

   cl_http_client=>create_by_destination(

    EXPORTING

      destination              = 'VENDAVO'    " Logical destination (specified in function call)

    IMPORTING

      client                   = lo_http_client    " HTTP Client Abstraction

    EXCEPTIONS

      argument_not_found       = 1

      destination_not_found    = 2

      destination_no_authority = 3

      plugin_not_active        = 4

      internal_error           = 5

      OTHERS                   = 6

  ).

 

* If you are using cl_http_client=>create_by_url use this code to supress and pass your

* HTTP basic authenication 

*  lo_http_client->propertytype_logon_popup = lo_http_client->co_disabled.

*  DATA l_username TYPE string.

*  DATA l_password TYPE string.

*  l_username = 'user'.

*  l_password = 'password'.

*  CALL METHOD lo_http_client->authenticate

*    EXPORTING

*      username = l_username

*      password = l_password.

 

CREATE OBJECT lo_rest_client

     EXPORTING

       io_http_client = lo_http_client.

 

   lo_http_client->request->set_version( if_http_request=>co_protocol_version_1_0 ).

 

   IF lo_http_client IS BOUND AND lo_rest_client IS BOUND.

     lv_url = 'salesorder'.

 

     cl_http_utility=>set_request_uri(

       EXPORTING

         request = lo_http_client->request    " HTTP Framework (iHTTP) HTTP Request

         uri     = lv_url                     " URI String (in the Form of /path?query-string)

     ).

 

* ABAP to JSON

     TYPES: BEGIN OF ty_json_req,

            salesorder TYPE string,

            type TYPE string,

            END OF ty_json_req.

     DATA: json_req TYPE ty_json_req.

 

     json_req-salesorder = '25252525'.

     json_req-type = 'Direct'.

 

DATA lr_json_serializer   TYPE REF TO cl_trex_json_serializer.

 

CREATE OBJECT lr_json_serializer  EXPORTING  data = json_req.

lr_json_serializer->serialize( ).

lv_body = lr_json_serializer->get_data( ).

 

* Or you can use this class as well

* lv_body = /ui2/cl_json=>serialize( data = json_req ).

 

* Converted JSON should look like this

* lv_body = '{ "salesorder":"25252525", "type":"Direct"}'.

 

     DATA: lo_json        TYPE REF TO cl_clb_parse_json,

           lo_response    TYPE REF TO if_rest_entity,

           lo_request     TYPE REF TO if_rest_entity,

           lo_sql         TYPE REF TO cx_sy_open_sql_db,

           status         TYPE  string,

           reason         TYPE  string,

           response       TYPE  string,

           content_length TYPE  string,

           location       TYPE  string,

           content_type   TYPE  string,

           lv_status      TYPE  i.

 

* Set Payload or body ( JSON or XML)

     lo_request = lo_rest_client->if_rest_client~create_request_entity( ).

     lo_request->set_content_type( iv_media_type = if_rest_media_type=>gc_appl_json ).

     lo_request->set_string_data( lv_body ).

 

 

CALL METHOD lo_rest_client->if_rest_client~set_request_header

       EXPORTING

         iv_name  = 'auth-token'

         iv_value = token number. "Set your header .

 

 

* POST

     lo_rest_client->if_rest_resource~post( lo_request ).

 

* Collect response

     lo_response = lo_rest_client->if_rest_client~get_response_entity( ).

     http_status = lv_status = lo_response->get_header_field( '~status_code' ).

     reason = lo_response->get_header_field( '~status_reason' ).

     content_length = lo_response->get_header_field( 'content-length' ).

     location = lo_response->get_header_field( 'location' ).

     content_type = lo_response->get_header_field( 'content-type' ).

 

     response = lo_response->get_string_data( ).

 

* JSON to ABAP

     DATA lr_json_deserializer TYPE REF TO cl_trex_json_deserializer.

     TYPES: BEGIN OF ty_json_res,

            token TYPE string,

            END OF ty_json_res.

     DATA: json_res TYPE ty_json_res.

 

     CREATE OBJECT lr_json_deserializer.

     lr_json_deserializer->deserialize( EXPORTING json = response IMPORTING abap = json_res ).

Introduction to ABAP Channels

$
0
0

Motivation

 

 

Most ABAP applications use polling techniques to achieve an event-driven communication.For pushing an event from an ABAP backend to a browser based user agent like SAPUI5, Web Dynpro, Business Server Pages (BSP) or WebGUI, a polling in multi-seconds interval is frequently used. This is a quite system resource consuming technique. In SAPGUI usually the timer control is used to detect an event in the ABAP backend system. ABAP Channels technology targets to replace such inefficient eventing based on polling techniques through push notifications based on publish-subscribe infrastructure and WebSockets in ABAP.

 

In which situations you will benefit from ABAP Channels? This blog describes the typical business usage scenarios: Say Goodbye to Polling: Real-Time Eventing in ABAP with ABAP Channels.

 

 

Overview

 

 

ABAP Channels infrastructure was delivered with SAP NetWeaver AS ABAP 7.40 support package 2 (SP2) for simple tests and prototyping and released with 7.40 support package 5 (SP5).

 

The basic idea of the ABAP Channels (see Figure 1) is the native support of interactive and collaborative scenarios based on event-driven architecture. The scope of ABAP Channels consists of the following communication channels:

 

 

  • ABAP Push Channel for bi-directional communication with user agents via WebSockets in ABAP. In the Figure 1 the documents tagged with letter "P" mark the ABAP Push Channel communication paths.
  • Publish/subscribe infrastructure for exchange of messages between either user sessions (Session C => Session A) or user session and user agents
    (Session C => User Agent B) residing on different application servers via ABAP Messaging Channels. In the Figure 1 the documents tagged with letter
    "M" mark the ABAP Messaging Channels communication paths.


                                 Picture6_2.png
                                             
Figure1: ABAP Channels support real-time eventing in ABAP

 

 

The ABAP Channels support the following use cases:

 

  • ABAP Push Channel (APC) The WebSocket integration in ABAP.
  • ABAP Messaging Channel(AMC): Eventing framework for message exchange between different ABAP sessions based on publish/subscribe channels.
  • Collaboration scenario: Using APC and AMC to push messages from ABAP sessions to WebSocket clients by binding publish/subscribe channels to a WebSocket connection.

 

    http://scn.sap.com/servlet/JiveServlet/showImage/38-89675-431447/amc_apc_collaboration_simple.gif


                                           Figure 2: ABAP Channels use cases in ABAP

 

 

General recommendations for usage of ABAP Channels

 

SAP recommendations for a common programming model for use of ABAP channels are:

 

  1. Use one WebSocket connection for collaboration scenario
  2. Use SAP Push Channel Protocol (PCP) messaging protocol                                                                                           

 

 

Use one WebSocket connection for collaboration

 

For collaboration scenarios which take use of WebSocket (ABAP Push Channel),it is recommended to share - whenever possible - a single WebSocket connection per client (e.g. browsertab/UI application) for consumption of different types of frequently updated content (feed): e.g. changes applied to a business object, or notification regarding incoming external information (news, weather, sport, chat, activation/deactivation of logs, traces, debugging, etc.).

 

This is because:

  • This will avoid the unnecessary creation of parallel WebSocket connections for each feed, which is a limited resource at the both communication sides i.e. at the client and the server.
  • Any additional WebSocket connection leads to performance degradation due to WebSocket protocol specific network traffic (e.g. due to keeping alive ping-pongs packages). Furthermore the lifecycle handling of the WebSockets leads to additional complexity at both communication sides.
  • Moreover the situation will worsen when in the UI dependencies between different feeds exist, e.g. because a UI area contains an aggregation of information provided by different feed providers.

 

Figure 3 shows roughly how the messages are exchanged between an UI application running in a browser and using WebSocket connection (ABAP Push Channel) to ABAP sessions. The ABAP Messaging Channels (AMC 1… 4) are bound to the ABAP Push Chanel (APC). Either the UI Client can act as feed producer sending notifications about business object changes on UI to the subscribed WebSocket clients (Sessions 1…4) or vice versa: ABAP Sessions can publish messages about business object changes to their respective AMCs and they will be pushed by APC to the subscribed WebSocket clients (UI areas of business objects BO 1…4)


                            Picture5.png


                                                   Figure 3: One WebSocket Connection (APC) for collaboration scenario

 

 

Use common messaging protocol Push Channel Protocol (PCP)

 

 

Starting with AS ABAP 7.40, SP05 it is recommended to send and receive messages in ABAP Channels in format of SAP's own Push Channel Protocol (PCP).

 

The Push Channel Protocol (PCP) is a message based protocol with message format similar to a HTTP message format.

 

The proposal of having a common messaging protocol is also valid for most of well-known service oriented infrastructures, e.g. SOA, REST or OData based services. This enables an application developer to publish and consume the services in the same way. Furthermore with this strategy an extension of the provided services is possible, as additional optional fields/metadata can be added to the existing message without breaking the downport compatibility rules.

 

In context of ABAP Channels the consumption of different feeds by a single WebSocket connection requires a common higher protocol and message type on top of WebSocket for those feeds which are pushed from server. This is necessary because in the WebSocket message handling at the UI client side should be a unique and reliable way to identify the different message types and to assign the appropriate consumer UI area. In worst case if the feeds do not
share the same protocol and each feed provider sends different message type, e.g. feed <f1> sends its messages as a kind of custom XML document, feed <f2> as JSON and feed <f3> as binary, it would be hardly possible to process the messages in a simple and reliable way in the WebSocket message handling. 

 

More about how to exchange messages in ABAP Channels with PCP will be published soon on SCN.

 

 

 

 

Using ABAP Channels

 

 

More detailed information about how to use ABAP Channels in the following series of blogs by Masoud Aghadavoodi Jolfaei

 

Book Tip: SAP Interface Programming

$
0
0

Hello community,

 

I am a friend of SAP interface programming, it is an exciting area with a lot of possibilities.

 

Dr. Michael Wegelin and Michael Engelbrecht published the 3rd edition of the book SAP Interface Programming in the SAP Press publishing house, you can find an introduction here. As far as I can see is the actual edition from 2014 only available in German language at the moment.

 

The book offers different perspectives of interface programming, from the langauges ABAP, C/C++, Java and C#, via RFC, RFC NW library, IDocs, ALE, SOAP and NW Gateway with OData. The description is comprehensive and understandable. It shows a lot of details of the actual programming languages and connection libraries with its possibilities. Though I often have been working in the area of interface programming, this book delivers interesting suggestions for deepening my knowledge. For an introduction or refreshing I can recommend the new edition of this book highly.

 

SAP_InterfaceProgramming.png

 

Enjoy it.

 

Cheers

Stefan

Connecting to external databases via RFC-server

$
0
0

Hello community,

 

On my recent project I have faced the requirement to communicate with external database (MSSQL).

It was impossible to use DBConnect because application server running on AIX. Attempt to use UDConnect ended here.

 

So, I decided to create RFC server, that allow to communicate with external database via ODBC.

 

I hope it may be useful for someone who faced the same requirement.

Also it may be useful as RFC programming example.

 

It’s not as convenient as “usual” usage of SQL in ABAP, but the advantage is there is no no need to create database table with the same name and identical type in the ABAP Dictionary of the local AS ABAP, as DBConnect does.

 

How to make it work: in this scenario we assume to connect with NorthWind database on MSSQL.

  • Download it from GitHub.
  • Create ABAP objects: Z_SQL_PARAM structure, Z_SQL_PARAM_TT table type, Z_SQL_EXEC Z_SQL_COMMIT, Z_SQL_ROLLBACK and Z_SQL_SEL2CORRESPONDING function modules, ZEXAMPLE report.
  • Set up RFC destination ZNORTHWIND in transaction SM59. Set Activation Type=Registered Server Program, Program ID= ZNORTHWIND.sm59.png


  • Set up RFC server. It can be done on the same machine where MSSQL running. At least you have to install MSSQL native client and Microsoft.VC80.CRT.
    1. Create some directory and put there rfcsrv_odbc.exe, sapnwrfc.ini and libsapucum.dll, sapnwrfc.dll from NWRFC.
    2. Edit sapnwrfc.ini :

DEST=DEST1 <-- this is the label for connection entry, pass it as first parameter when run rfcsrv_odbc.exe

ASHOST=XX.XX.XX.XX <-- sap application server ip-address

CLIENT=100 <-- client number

TRACE=1 <-- trace level

USER=BATCH <-- user name for rfc server to connect with SAP AS

PASSWD=****** <-- user's password

LANG=EN

SYSNR=00 <-- system number

TPNAME=ZNORTHWIND <-- Program ID.


  • Set up ODBC DSN for NorthWind database. Choose "MSSQL native client" driver type.
  • Run rfcsrv_odbc.exe, specify only first parameter DEST1: rfcsrv_odbc.exe DEST1 It pops up connection string build dialog. Choose NorthWind DSN, user name, password, and other options, click OK. RFC server prints out complete connection string. Copy it from console.
  • Run rfcsrv_odbc.exe, specify  DEST1 as first parameter and connection string as second parameter (see run.bat): rfcsrv_odbc.exe DEST1 "CONNSTR".If everything fine, it prints out the following:

cmd.png

If not - see .trc file for details.

  • In transaction SM59 perform connection test for ZNORTHWIND.
  • Run (and examine) example report ZEXAMPLE.

 

Best regards,

Nick.

Client proxy blank response

$
0
0

Have you ever tried to consume a external WS in SAP ( Calling the client proxy -> Method ) filling all the parameters and you receive a blank response?

 

 

Below I indicate the steps to consume the external WS:

 

    -  Create Client Proxy as usual in SE80

    -  Create Logical port as usual in Soamanager

  

 

    Imagine that this WS method ( called concatenate ) has two inbound parameters:

      

        - Param1

        - Param2

      

    And it concatenates Param1 and Param2

  

    - Go to SE80 select the client proxy and test it with the client proxy test tool, indicating the Logical port and checking the set extended xml handling checkbox. After this SAP shows the request in xml format.

    Export the request content in a local file.

 

  

 

Once the xml format request is downloaded, it's possible to call the WS using the code below.

 

 

Report ZWS_CALL.

 

 

*Data declaration

data: lo_sys_exception   type ref to CX_AI_SYSTEM_FAULT ,

      err_string type string.

 

data: lv_Req type STRING,

      lv_resp type STRING.

 

data: lv_class_name   type SEOCLNAME,

      lv_method_name  type SEOCLNAME,

      lv_log_port     type PRX_LOGICAL_PORT_NAME,

      lv_request      type XSTRING,

      lv_handling     type ABAP_BOOL,

      lv_response     type XSTRING,

      lv_error        type xstring,

      lv_exce         type STRING.

 

data: lv_param1 type string value 'This is a'.

      lv_param2 type string value 'Webservice test'.

 

data: v_param1 type string,

      v_param2 type string.

 

 

 

concatenate ' <n0:param1>' lv_param1 '</n0:param1>' into v_param1. * parameter 1

concatenate ' <n0:param2>' lv_param2 '</n0:param2>' into v_param2. * parameter 2

 

lv_class_name  = 'ZCO_CONCATENATE'. " client proxy generated class

lv_method_name = 'CONCATENATE'.        " method name

lv_log_port    = 'C_LOG_PORT'.      " logical port

lv_handling    = 'X'.               " set extended xml handling

 

* build the request in xml format as it's in the se80 test tool ( downloaded file )

concatenate '<n0:concat xmlns:n0="http://XXXXXX.XXXXXXX.CCCCCCC" xmlns:prx="urn:sap.com:proxy:xxx:/xxx/xxxxxxxxxxxxxxxxx:700:2008/01/11">'

            v_param1

            v_param2

            '</n0:login>' into lv_req separated by cl_abap_char_utilities=>cr_lf.

 

* transform the request into xstring format

lv_request = cl_proxy_service=>cstring2xstring( lv_req ).

 

data: system_fault type ref to cx_ai_system_fault,

      system_error type ref to cx_xms_system_error,

      sus type ref to CX_XMS_SYSERR_PROXY.

 

 

* call the method using the CL_PROXY_ADAPTER_TEST

TRY.

    CALL METHOD CL_PROXY_ADAPTER_TEST=>OUTBOUND_TEST

      EXPORTING

        CLASS_NAME            = lv_class_name

        METHOD_NAME           = lv_method_name

        LOGICAL_PORT_NAME     = lv_log_port

        REQUEST_DATA          = lv_request

        EXTENDED_XML_HANDLING = lv_handling

      IMPORTING

        RESPONSE_DATA         = lv_response

        ERROR_DATA            = lv_error

        EXCEPTION_CLASS_NAME  = lv_exce.

 

  CATCH CX_XMS_SYSERR_PROXY into sus.

    message sus type 'I'.

    exit.

  catch cx_xms_system_error into system_error.

    message system_error type 'I'.

    exit.

  CATCH CX_SXML_SERIALIZATION_ERROR .

  catch cx_ai_system_fault into system_fault.

    message system_fault type 'I'.

    exit.

  CATCH CX_AI_APPLICATION_FAULT .

ENDTRY.

 

* TRANSFORM THE RESPONSE FROM XSTRING TO STRING AND PROCESS IT. HERE WE SHOULD FIND THE RESULT OF THE CONCATENATE OPERATION.

 

lv_resp = cl_proxy_service=>XSTRING2CSTRING( lv_response ).

 

 

Hope it helps.

 

Regards

 

Jon

ABAP Push Channel with BSP application

$
0
0

Hi, this is my first blog.

 

There are many great blogs about APC. Here I just want to show how to get notification in a bsp application using APC without refreshing the web page.

to understand  APC  better please go through the following series of blogs by  Masoud Aghadavoodi Jolfaei

first

 

 

 

  • Here I just want to concentrate on the application part more than explanation part it was explained briefly in that blog.

 

  • So I'm going to create APC, AMC and a bsp application

 

  • Procedure:

 

  • Create an APC( ABAP Push Channel):


  • Execute t-code sapc right click create on the context menu. Enter 'zapc_test' in the popup and push continue.

first.png

  • Select local object.

 

  • Enter Description and click on the generate class and service.

 

second.PNG

  • click on the continue button for the information popup.


  • double click on the class and click enter into the class.

 

  • Now redefine on_start and on_message methods of the class.

 

third.PNG

 

  • leave those methods empty for now

 

  • and active both the class and the APC.

 

  • Create an AMC:

 

  • Execute t-code samc and click on the create in the context menu. Enter 'zamc_test' in the input and click on the continue.     fourth.PNG
  • click the local object when the next popup shows up.

 

  • Enter some description in the description field (eg: AMC test).

 

  • under channels enter your channel as '/ping' and click on enter.

 

  • In the Channel details channel will be updated and now enter Message Type ID as TEXT,then Activity scope as system.

 

  • Under Authorized program give the class which you generated for the APC and prog. type as CLAS and under the activity select the Receive via APC Websocket.
  • fifth.PNG
  • Now activate the AMC.

 

  • Double click on the class and navigate into the generated class.

 

  • now click on the on_start method.

 

  • click on the display<->change button or press cntl+f1 make it into change mode.

 

  • Paste the following code in the method
  • TRY.

               i_context->get_binding_manager(

    )->bind_amc_message_consumer(

                   i_application_id 'ZAMC_TEST'

                   i_channel_id     = '/ping' ).

    CATCH cx_apc_error INTO DATA(exc).

    MESSAGE exc->get_text( ) TYPE 'X'.

    ENDTRY.

  • click on pretty printer and activate the code.
  • Open on_message method and paste the following code.

TRY.

         DATA(lv_msg) = i_message->get_text( ).

         CAST if_amc_message_producer_text(

               cl_amc_channel_manager=>create_message_producer(

                 i_application_id = 'ZAMC_TEST'

                 i_channel_id     = '/ping' )

          )->send( i_message lv_msg  ) ##NO_TEXT.

 

       CATCH cx_amc_error cx_apc_error INTO DATA(lref_exc).

         MESSAGE lref_exc->get_text( ) TYPE 'X'.

ENDTRY.

 

Create a report for sending the content to bsp application:

 

  • execute t-code se38 and create a report with name 'zapc_message_send'.
  • Enter some title and type as Executable program. select status as Test program and click on save.
  • Select local object and click on continue.
  • paste the following code.

PARAMETER:p_bar1 TYPE string,

                      p_bar2 TYPE string,

                      p_bar3 TYPE string.

DATA: lv_text TYPE string.

CLASS lcl_demo DEFINITION.

   PUBLIC SECTION.

     CLASS-METHODS main.

ENDCLASS.

CLASS lcl_demo IMPLEMENTATION.

   METHOD main.

     TRY.

         CONCATENATE p_bar1 p_bar2 p_bar3 INTO lv_text SEPARATED BY '~'.

         CAST if_amc_message_producer_text(

                cl_amc_channel_manager=>create_message_producer(

                  i_application_id = 'ZAMC_TEST'

                  i_channel_id     = '/ping' )

           )->send( i_message = lv_text ) ."|Static text| ).

       CATCH cx_amc_error INTO DATA(lref_text_exc).

         cl_demo_output=>display( lref_text_exc->get_text( ) ).

     ENDTRY.

   ENDMETHOD.

ENDCLASS.


START-OF-SELECTION.

   lcl_demo=>main( ).

Create a BSP application:

  • Execute t-code se80 and create a bsp application with some application name 'ZAPC_TEST'. give some short description and save.

sixth.PNG

then from the context menu of the bsp application, create a page.

seventh.png

  • When the next popup shows up enter your page name as start.bsp and some description and select page type as page with flow logic and click on continue,

eighth.PNG

  • In the start.htm page remove the default code and paste the following code in it.

<%@page language="abap"%>

<html>

   <head>

     <script src="http://code.jquery.com/jquery-latest.min.js"></script>

     <script type="text/javascript" src="http://rawgit.com/lampieg/LiveGraph/master/jquery.livegraph.js"></script>

     <script type="text/javascript">

     <%

       CONSTANTS lv_path TYPE string VALUE `/sap/bc/apc/sap/zapc_test`.

       DATA(lv_location) = cl_http_server=>get_location( application = lv_path ).

       IF lv_location CS 'https'.

         REPLACE FIRST OCCURRENCE OF 'https' IN lv_location  WITH 'wss'.

       ELSE.

         REPLACE FIRST OCCURRENCE OF 'http' IN lv_location WITH 'ws'.

       ENDIF.

*      CONCATENATE lv_location lv_path '?amc=x' INTO DATA(lv_url).

       CONCATENATE lv_location lv_path INTO DATA(lv_url).

       CONDENSE lv_url.

     %>

       var gv_values;

       var ws ;

       function WebSocketDemo(para)

       {

         if ("WebSocket" in window)

         {

            if (para == "open" )

            {

               ws = new WebSocket("<%=lv_url%>");

 

            };

            if (para == "send" )

            {

               ws.send( document.getElementById("messageInput").value );

            };

            if (para == "close" )

            {

               ws.close( );

            };

            ws.onmessage = function (evt)

            {

             var inputMessage = evt.data;

             gs_values = inputMessage.split("~");

             var lv_tab1 = Number(gs_values[0]);

             var lv_tab2 = Number(gs_values[1]);

             var lv_tab3 = Number(gs_values[2]);

             var updateData = {

                 tb1: {

                     value: lv_tab1

                 },

                 tb2:{

                     value: lv_tab2

                 },

                 tb3:{

                     value: lv_tab3

                 }

             };

             $('#page').liveGraph('update', updateData);

            };

            ws.onclose = function()

            {

               alert("WebSocket closed");

            };

         }

         else

         {

            alert("Browser does not support WebSocket!");

         }

       }

     </script>

      <style>

          div[bar="tb3"] span {

             background-color: #fff877;

         } /* ONLY MATCHES THE FIRST BAR */

         div[bar="tb2"] span {

             background-color: #9356ff;

         } /* ONLY MATCHES THE SECOND BAR */

         div[bar="tb1"] span {

             background-color: #c3ff4e;

         }

     </style>

   </head>

   <body style="font-family:arial;font-size:80%;" onload = "WebSocketDemo('open')">

   <div id="page"></div>

     <script type="text/javascript">

 

     $(document).ready(function() {

         var originalData = {

             tb1: {

                 label: "Bar 1",

                 value: 50

             },

             tb2: {

                 label: "Bar 2",

                 value: 20

             },

             tb3: {

                 label: "Bar 3",

                 value: 90

             }

         };

         $('#page').liveGraph({

             height : 350,

             barWidth : 100,

             barGapSize : 2,

             data : originalData

         });

 

     });

 

</script>

   </body>

</html>

 

  • Activate the complete bsp application.

 

 

  • Open the AMC 'ZAMC_TEST' and click on the insert line under the Channel Details section.

ninth.PNG

  • give authorized Program as ZAPC_MESSAGE_SEND , prog. type as REPS and activity as SEND.
  • activate your amc.

tenth.PNG

Now we are almost done we just need to create a report to run the bsp application. It is possible to execute the bsp application directly but it open the bsp application in the internet explorer, but we have to run the bsp application in chrome browser. so make sure that your default browser is chrome.

 

Creating a report to run the bsp application:

 

  • Run the t-code SE38 and give input as ZBSP_EXECUTE and click on create.
  • In the next popup provide some title, select type as Executable program and status as test program.
  • Click on the save button
  • Paste the following code in the report

DATA: lv_url        TYPE string,

       lv_urlc(4096) TYPE c,

       lt_parms      TYPE tihttpnvp.

*-- Create URL to BSP Application

CALL METHOD cl_http_ext_webapp=>create_url_for_bsp_application

   EXPORTING

     bsp_application      = 'ZAPC_TEST'

     bsp_start_page       = 'START.HTM'

     bsp_start_parameters = lt_parms

   IMPORTING

     abs_url              = lv_url.

 

*-- Call the BSP Application in the default Browser

lv_urlc = lv_url.

CALL FUNCTION 'CALL_BROWSER'

   EXPORTING

     url                    = lv_urlc

     window_name            = 'BSP'

     new_window             = ' '

   EXCEPTIONS

     frontend_not_supported = 1

     frontend_error         = 2

     prog_not_found         = 3

     no_batch               = 4

     unspecified_error      = 5

     OTHERS                 = 6.

 

IF sy-subrc <> 0.

   MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno

           WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.

ENDIF.

  • Now run the report 'ZBSP_EXECUTE' and 'ZAPC_MESSAGE_SEND'.
  • first report open your chrome browser show a bar graph.

twelfth.PNG

  • second report gives you three paramenters.

thirteenth.PNG

  • try to give some three values and execute the report (press f8).
  • Graph in the browser should get automatically updated without any refresh.

 

 

I hope that helps. I'm open for your comments, suggestions, question and all. Please leave your comments.

 

 

Thanks,

Santosh.


Posting Key (BSCHL) and debit/credit indicator (SHKZG) determination logic for IDOC type ACC_DOCUMENT03

$
0
0

Description:


When using the basic type ACC_DOCUMENT03, in order to pass the values of posting key and credit/debit indicator the respective fields are not available. This blog determines how these are being determined automatically and the steps to check those required fields.


Solution:


This can be handled even though we do not pass the credit debit indicator or posting key. The BAPI which we are using for the posting the FI document itself does this.

BAPI_IDOC_INPUT1 is the BAPI used for individual posting.


1. In segment E1BPACCR09 (Currency Item):

    We need to pass the amount in AMT_DOCCUR field.

CurrancyItems.PNG

              Figure 1: Segment E1BPACCR09 (Currency Item)

 

In the above segment pass the amount in field AMT_DOCCUR with ‘-’ negative sign for credit and for debit there is no need to attach ‘+’ sign.


Example


In the below snapshot the amount ‘349.9000-’ indicate that amount should be credited and the amount ‘284.4500’ indicate that amount should be debited.

 

    Amount.PNG

                                     Figure 2:  Amount in Segment E1BPACCR09

                               

 

2. Depending on amount the debit/credit indicator is fetched. The code for the same is written in FORM ‘PROCESS_ACCCR’.

 

    As we know for debit/credit indicator there are two values which are fixed i.e. ’S’ and ‘H’.

          S – Debit.

          H - Credit.


If amount is greater than ‘0’ then field SHKZG (debit/credit indicator) will hold the ‘S’ Value which indicate debit and if amount is less than zero then field SHKZG (debit/credit indicator) will hold the ‘H’ Value which indicate Credit.

 

Below snapshot shows the code written in the form routine ‘PROCESS_ACCCR’.

   

Debit_credit.PNG

                            Figure 3: Fetching Debit/Credit Indictor (SHKZG)

 

 

3.  Once the debit/credit indicator is fetched, the account type should be fetched. Account type is fetched on the basis of parameter field in it_bapi_accit. The IDOC data it is split-ed into 4 internal tables as account_receivable, account_payable, account_gl and Account_tax. These internal tables are checked and then if the data is present in their respective fields then the parameter field is hard-coded with ACCOUNTRECEIVABLE, ACCOUNTPAYABLE, ACCOUNTGL, and ACCOUNTTAX respectively.

 

Below Snippet shows how the values are getting populated in field parameter. This code is written in form routine ‘FILL_BAPI_ACCIT’.

   

AccountGL_hardcoded.PNG

                                Figure 4: Filling parameter 

 

Following are the account types which are present in SAP:

  1. A – Asset
  2. D – Customer
  3. K – Vendor
  4. M – Material
  5. S – General ledger


Depending on the values in gs_bapi_accit-parameter the account type is chosen.If gs_bapi_accit-parameter is holding value as ‘ACCOUNTGL’ then ‘S’ account type will be hard-coded.

    ACCOUNTTYPE.PNG

                              Figure 5: Fetching Account type (KOART)

 

And for remaining parameters please check the below code which is written in form routine ‘FILL_ACCOUNTTYPE’.

 

Code:

CASE gs_bapi_accit-parameter.
WHEN 'ACCOUNTRECEIVABLE'.
IF gs_accit-kunnr IS INITIAL AND
NOT gs_accit-hkont IS INITIAL.
gs_accit
-koart = 'S'.
gs_accit
-taxit = ' '.
ELSE.
gs_accit
-koart = 'D'.
IF gs_bapi_accit-actv_account = '2'.
PERFORM koart_v_active(saplfaci) IF FOUND
USING    gs_acchd-awtyp
CHANGING l_koart.
IF NOT l_koart IS INITIAL.
gs_accit
-koart = l_koart.
ENDIF.
ENDIF.
gs_accit
-taxit = ' '.
gs_accit
-xfilkd = 'X'.
PERFORM check_if_initial
USING gs_bapi_accit-parameter
gs_bapi_accit
-tabix:
'CUSTOMER  ' gs_accit-kunnr.
IF gs_accit-shkzg = 'H' AND                        "note 1596139
gs_acchd
-awtyp <> 'LOANS'.                      "note 1596139

          gs_accit-rebzg = 'V'.
ENDIF.
ENDIF.

WHEN 'ACCOUNTPAYABLE'.
IF gs_accit-lifnr IS INITIAL AND
NOT gs_accit-hkont IS INITIAL.
gs_accit
-koart = 'S'.
gs_accit
-taxit = ' '.
ELSE.
gs_accit
-koart = 'K'.
gs_accit
-taxit = ' '.
gs_accit
-xfilkd = 'X'.
IF gs_acchd-awtyp <> 'BEBD' AND
gs_acchd
-awtyp <> 'BERD' AND
gs_acchd
-awtyp <> 'BERE' AND
gs_acchd
-awtyp <> 'ENTD'.
PERFORM check_if_initial
USING gs_bapi_accit-parameter
gs_bapi_accit
-tabix:
'VENDOR_NO ' gs_accit-lifnr.
ENDIF.
IF gs_accit-shkzg = 'S' AND                        "note 1596139
gs_acchd
-awtyp <> 'LOANS'.                      "note 1596139
gs_accit
-rebzg = 'V'.
ENDIF.
ENDIF.

WHEN 'ACCOUNTGL'.
IF gs_accit-koart IS INITIAL.
gs_accit
-koart = 'S'.
ENDIF.
gs_accit
-taxit = ' '.
IF gs_accit-kstat IS INITIAL AND
gs_accit
-koart = 'S'.                "Note 1503626
PERFORM check_if_initial
USING gs_bapi_accit-parameter
gs_bapi_accit
-tabix:
'GL_ACCOUNT' gs_accit-hkont.
ENDIF.

WHEN 'ACCOUNTTAX'.
PERFORM process_accit_tx
USING gs_bapi_accit-tabix.

WHEN OTHERS.
RAISE wrong_linetyp.
ENDCASE.

 

4.        Based on the debit/credit indicator and account type the posting key is fetched.

Now in this case for account type i.e. KOART we are having value as ‘S’. i.e. GL. And in debit/credit indicator i.e. SHKZG. we are having value as ‘S’ then the posting key is fetched as 40 i.e. Debit.

   

Posting_key.PNG

                                Figure 6: Fetching Posting Key (BSCHL)

Summary:

  • As there are no fields for passing the debit/credit indicator and posting key in IDOC ACC_DOCUMENT03 but this can be handled using the BAPI ‘BAPI_IDOC_INPUT1’, the only thing we need to take care of is ‘just pass the amount with negative sign if it should be credited’. Rest all things are handled by this standard BAPI as mentioned in above steps.

 

 

Hope this will help.

How To Export SAP Table Content to SQLite Data Containers

$
0
0

Hello community,

 

I create different ways to export SAP table content via ABAP to SQLite databases.

 

The first way is via a COM library, called SQLite3COM. With this library you can direct execute SQL commands in SQLite databases on the presentation server. On this way you can export the data. The second way is via SQL statements in a text file. You can read this text file with sqlite.exe and create your SQLite database on this way. To use this ways comfortable from ABAP I programmed a class with three public methods:

  • GetTable2SQLite - to export the table direct via the COM library to an SQLite database.
  • GetTable2File - to export the table indirect via an SQL command file, which can be imported to an SQLite database.
  • AltGetTable2File - the same as GetTable2File, but with another method to create the file on the presentation server.

 

Here the class definition:

 

Methods GetTable2SQLite  Importing    TableName Type String    WhereClause Type String    DbName Type String    UseExistingDb Type i    OverWriteExistingTable Type i.
Methods GetTable2File  Importing    TableName Type String    WhereClause Type String    FileName Type String    SQLiteSQL Type i    OverWriteConfirm Type c.

You must name the table, an optional where clause, the name of the database or file and two flags to control the processing.

 

You can download the full documented library and the ABAP sources here.

Also you need the SQLite library and program from here.

You can find a video description here: SQLite3COM - YouTube

 

Enjoy the possibility to export SAP tables into SQLite databases. With this way you can use the content easily on different platforms and in different environments,

 

Cheers

Stefan

HCP, ABAP and websocket part 1

$
0
0

Foreword

I will be writing a four part blog about a collaborative scenario implicating the HANA Cloud Platform and an ABAP backend Server. The blog posts will have the following content (summary):

  1. Part 1: Introduction and websocket on HCP
  2. Part 2: Moving the websocket app to ABAP
  3. Part 3: Realising cross-UI communication (dynpro and UI5)
  4. Part 4: Putting it all together: HCP - ABAP communication

Hopefully, I will manage to write one blog each week .

I will share the source code for the application on my git: epmwebsocket repository.The source code for this week is in this commit: e3fa300

If anyone tries to deploy a copy to the cloud, use Java Web Profile 6, latest version (currently 2.63), JRE 7.

 

Introduction

As everything else, this protocol has its downsides when compared to plain HTTP:

  • security is "more vulnerable" (the "elevated" Version of WS, WSS is not as good as HTTPS)
  • the communication is done at a lower level
  • there are not as many proved techniques for building applications, not so many libraries, etc

So, as a personal opinion: WS should be used to issue commands to the Client and HTTP requests should be used to Transfer the actual data (e.g. via an OData Service).

 

WS on HCP

Our Goal is to build a UI5 application which will react dynamically when changes occur in the underlying OData Services. More specifically, I have made an app which auto-refreshes its data model whenever an user deletes an entity (we will Focus on this simple Scenario as an example, more complex cases can of course be built in a similar fashion). The data model will be SAP's EPM model and mock data will be borrowed from the sap.m Explored apps.

 

Java and OData (using Olingo)

First, I have built the underlying OData Service. I used JPA for the persistency and the Apache Olingo library for implementing the Service itself. I had to make a class for each DB table (= a JPA entity) and to add these classes in a persistence unit.

epmJPA.png

For simplicity, I used the existing classes and annotations with minimal modification (i.e. no business logic was attached to the Service). The only modification from the straight-forward approach shown in this blog is that I wrapped the ODataSimpleProcessor in a delegator (to be able to hook into the delete entity calls).

 

public class ODataSingleProcessorWrapper extends ODataSingleProcessor {  private ODataSingleProcessor delegate;  public ODataSingleProcessorWrapper(ODataSingleProcessor delegate) {       this.delegate = delegate;  }  //All the delegated methods follow...

I also included an Initializer class (which is actually a ServletContextListener) to fill in mock data each time the application is started. This will also be useful later on. The OData service could already be tested, by deploying the app and accessing the service's root URL in the browser (and maybe playing around with some requests).

 

A simple UI5 interface

After this, I built a suitable user interface to display the data, which should at least support the Delete operation. I have used a slightly modified Fiori Master-Detail Application template for generating this user Interface.

I also needed to include the logic for handling the WS Connection. With the use of the pre-existing UI5 WS library, this was pretty simple. This was implemented in the MessageHandler class (source), where I have reused some code written a while ago, which has 2 extra features:

  • A cooldown on the refresh Operation (i.e. to not flood the Server with requests)
  • An ignore next flag (which actually prevents the app from refreshing when it triggers a delete -- refresh is automatically done then, so we would in fact refresh the data twice)

For the moment, I had no WS connection URL for the application to use: this was done in the next step.

 

Creating the WS Endpoint

The Endpoint is a Java class which manages the Server side of the WS communication. The basic life cycle hooks of such connections are (these are method level annotations in the javax.websocket package):

  • onStart
  • onMessage
  • onClose
  • onError

For simplicity, I made a very simple Endpoint, which just keeps a synchronized collection of all sessions (connections with clients) and publishes a simple message to all of the at once. The publishing method (sendRefresh) is called when the OData Service DELETE entity method is called (that's why the delegate OData processor was created).

@ServerEndpoint(value = "/ws")
public class Endpoint {  private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<Session>());  public static void sendRefresh() {       for (Session s :  sessions) {            try {                 s.getBasicRemote().sendText("{\"action\": \"refresh\"}");            } catch (IOException e) { /* logging */ }       }  }  @OnOpen  public void open(Session session) {       sessions.add(session);  }  @OnClose  public void close(Session session) {       sessions.remove(session);  }
@OnError  public void error(Throwable e) { /* logging */ }
}

The URL used in the ServerEndpoint annotation was also updated in the UI5 application.

 

Result

When the UI5 application deletes a row, the OData processor instructs the Endpoint to send a "refresh" message to all Clients, which the in turn call the refresh method on their OData models.

We can very simply test if it works: we just need to open the app in two different tabs and delete one row in one of the tabs. The application should automatically refresh the other tab's data model (we should see the row disappear).

Untitled.jpg

That's about for this week, next week we will see how we can port this application to run on an ABAP System. See you next week

HCP, ABAP and websocket part 2

$
0
0

This is the second part of my 4-part blog series about HCP, ABAP and websocket(s). You can find the first part here: HCP, ABAP and websocket part 1 (contains links to all other parts).

This week, we will see how I managed to move the application from part 1 to an ABAP system.

The code for this week can be found in this commit: 9119b2d.

 

Creating the OData Service (NW Gateway)

Let's take it in the same order as in the first part. So, I had to make the OData service using the available mechanisms on the AS ABAP. Therefore, I used the SAP Netweaver Gateway (tcode SEGW) to build the service. First, the data model was created. Luckily, the GW offers the possibility to import it from an metadata file. So I just accessed the $metadata URL of the original service (which we made in part 1) and saved it to a file. Then I uploaded it to the GW (left click on "Data Model" -> Import -> Data Model from File) and the data model was finished.

Untitled.png

Now, for the service implementation I did something not-so-pretty. Normally, the data should be pulled from RFCs/BAPIs or some other decoupled source, but I did it in some other, "not recommended" way. This is because this blog does not focus on the GW part (there is a separate space just for that: SAP NetWeaver Gateway Developer Center) and we would wish to make things a little faster.

So I proceeded to generate the classes and register the service. After this, I went to the MPC (model provider) class and saw what local data types (structures and table types) were generated. I used them as a model to create transparent tables (DB) for the entities. These tables will store the mock data used by the app.

Untitled.png

I also made a static method in the DPC_EXT (data provider - extension) class to populate the database. The get_entity, get_entity_set and delete_entity methods for the entities were also be overridden. In these methods, I simply did selects and deletes from the DB (this is the "un-recommended" coding -- DB operations should not be done directly in the DPC_EXT).

To test this we open the GW again, right click on the OData Service Maintenance entry and choose "Gateway Client" (and play a little with some OData requests to see if they work).

Untitled.png

 

The UI5 app

The next step would be to adjust the UI5 app to work on the AS ABAP. The only modification really necessary are: to change the OData and the WS URLs (the WS URL is not known yet, I will update it later) and to simply deploy it to the BSP repository. But, we would also like the app to still work in the cloud (i.e. to not have 2 different versions of the same app -- one for the AS ABAP and one for the HCP), so I just used a simple URL parameter which acts as a switch between these two sets of URLs. I called this parameter "location": by default the app uses the HCP URL set, but if the "location" parameter equals "abap", it uses the AS ABAP URL set. This coding is done in the Component.js file:

 

config : {            resourceBundle : "i18n/messageBundle.properties",            serviceConfig : { // cloud service config            name: "",            serviceUrl: "model.svc/"        },  ws: {                  //cloud WS config      url: "./ws",      cooldown: 1000  },  abapServiceConfig : {      name: "",      serviceUrl: "/sap/opu/odata/SAP/Z<GW_SERVICE_NAME>/"  },  abapWs: {      url: "/sap/bc/apc/sap/z<push_channel_name>",      cooldown: 1000  }
},
//...
var p = getQueryParams(document.location.search);
if (p.location == "abap") {      mConfig.serviceConfig = mConfig.abapServiceConfig;      mConfig.ws = mConfig.abapWs;
}

 

The WS Endpoint

The last step is to create the WS "part" of the application. This can be done using the ABAP push channel (APC) and the ABAP messaging channels (AMC). More information can be found in this document ABAP Channels Part 1: WebSocket Communication Using ABAP Push Channels .

First I made a push channel and create an empty protocol implementation class.

Untitled.png

Then, I created an messaging channel and listed the APC class and the DPC_EXT class as authorized objects.

Untitled.png

In the APC class, I wrote some simple code for the onMessage / onStart methods: just simply publish / subscribe to the AMC. The APC can be tested very simply at this point, by just using the "Test (F8)" button in SE80.

Untitled.png

I also updated the DPC_EXT class: added a new static method (I called it refresh), which is used to send a "refresh" message on the AMC previously created (and subsequently on the APC).

As a last step, the WS connection URL from the UI5 app was also updated accordingly (to the APC's URL).

 

We are done. Now the application should run exactly like it does on the HCP (only the "location" URL parameter must be set accordingly). Next week, we will attempt to link the UI5 application (on the AS ABAP) with a classic dynpro screen.

HCP, ABAP and websocket part 3

$
0
0

This is the third part of my blog series about HCP, ABAP and websocket. You can fiend the first part (including the list of parts) here: HCP, ABAP and websocket part 1.

In this part,things will start to get more interesting: this week we aim to propagate the changes (deleted rows) from the UI5 application (from last week) to a classic dynpro and vice versa. The source code for this week is in this commit: ebe3120

 

The ABAP Report

I started by making a report. I just declared a table of rows like the DB table structure and created a simple procedure (or it could also be a local class method) to fill in the table by doing a SELECT on the DB: I called it load_data.

 

FORM load_data.  CLEAR: gt_products.  SELECT * FROM zdemo_epm_ws_prd INTO TABLE gt_products.
ENDFORM.

 

After this, I just created a screen inside this report; the screen will contain only one custom control (I named it ALV). A new GUI status should also be made, with the back / cancel / exit buttons mapped to a simple 'EXIT' command and another button for the "delete selected row" operation (command = 'DELETE').

In the PBO module I added some simple code for initializing a cl_gui_alv_grid object (which will be displayed in the custom control) and a call to the load_data procedure.

In the PAI module, I wrote code for exiting the app (in case the 'EXIT' command was given) and for deleting a row from the DB and reloading the data (if 'DELETE' was given).

 

MODULE user_command_1000 INPUT.  CASE gv_ok_code.    WHEN 'EXIT'.      LEAVE PROGRAM.    WHEN 'DELETE'.        gr_alv_ctrl->get_selected_rows(          IMPORTING            et_index_rows =  DATA(lt_sel_rows)        ).        IF lines( lt_sel_rows ) > 0.            DATA(ls_product) = gt_products[ lt_sel_rows[ 1 ]-index ].            DELETE zdemo_epm_ws_prd FROM ls_product.            DELETE gt_products INDEX lt_sel_rows[ 1 ]-index.        ELSE.            MESSAGE 'No line is selected.' TYPE 'W'.        ENDIF.  ENDCASE.
ENDMODULE.

 

At this point, the report can be tested: it should display all the data from the tables and should delete the selected row when we press the delete button.

Capture.JPG

 

Establishing the communication

So, up until now, we have a UI5 application which can respond dynamically to changes to the data done by itself and a ABAP report which doesn't respond dynamically at all.

First let's look into the ABAP --> UI5 scenario: when we delete a row on the dynpro screen, it should be shown on the UI5 application. This is not that complicated to do. Firstly, I made a new procedure to send a refresh message on the AMC. I also made sure to add the report to the list of authorized programs in the AMC.

 

FORM send_refresh.    DATA: lo_producer TYPE REF TO if_amc_message_producer_text.    TRY.        lo_producer ?= cl_amc_channel_manager=>create_message_producer(          i_application_id = 'Z<AMC_APP_NAME>' i_channel_id = '/demo'        ).        lo_producer->send( i_message = zcl_zdemo_epm_ws_dpc_ext=>gv_refresh_message ).      CATCH cx_amc_error INTO DATA(lx_amc_error).        MESSAGE lx_amc_error->get_text( ) TYPE 'E'.      CATCH cx_apc_error INTO DATA(lx_apc_error).        MESSAGE lx_apc_error->get_text( ) TYPE 'E'.    ENDTRY.
ENDFORM.

 

Then I called this procedure after I deleted the row from the DB table (I had to also add a COMMIT WORK statement before this procedure call, to ensure that the other clients don't read the deleted row from the table).

 

The reverse scenario is a little tricky: we will need to trigger a PAI / PBO cycle to actually show the refreshed data and we also have to "find out" when such an update occurs. The statement WAIT FOR MESSAGING CHANNELS can be used to wait for a message to come. But if we would use this directly in a PAI / PBO module, the screen would freeze, waiting for a message to come. We want the report to be responsive, so this is out of the question.

The solution I have found is to wrap this instruction inside a aRFC call.

 

The function module

So, we need a function module for the aRFC. This will have a simple local AMC consumer class and just wait for a message to come. I also included an upper threshold for the amount of time that the FM waits for the messaging channels. Note that the FM should be remote enabled (to allow RFC calls).

 

FUNCTION ZDEMO_FM_REFRESH  EXPORTING    VALUE(EF_TIMEOUT) TYPE BOOLEAN.  DATA: lo_receiver_text TYPE REF TO lcl_amc_test_text.  ef_timeout = abap_true.  CLEAR gt_message_list.  TRY.      DATA(lo_consumer) = cl_amc_channel_manager=>create_message_consumer(          i_application_id = 'Z<AMC_APP_NAME>' i_channel_id = '/demo'      ).      CREATE OBJECT lo_receiver_text.      lo_consumer->start_message_delivery( i_receiver = lo_receiver_text ).    CATCH cx_amc_error INTO DATA(lx_amc_error).      MESSAGE lx_amc_error->get_text( ) TYPE 'E'.  ENDTRY.  WAIT FOR MESSAGING CHANNELS UNTIL lines( gt_message_list ) >= 1 UP TO 120 SECONDS.  IF lines( gt_message_list ) > 0.    ef_timeout = abap_false.  ENDIF.
ENDFUNCTION.

 

Putting it together

So now that we have the FM, we can use it in the report. I simply added an aRFC call to this FM. We need a form again, to be processed when the aRFC returns its result. In order to trigger the PAI / PBO, we have to set a new ok_code using the SET USER-COMMAND instruction. I have used two different commands: one for when the FM returns because a message was received and one for when the FM returns because the maximum time limit was exceeded.

 

FORM return_form USING taskname.  DATA lf_timeout TYPE abap_bool.  RECEIVE RESULTS FROM FUNCTION 'ZDEMO_FM_REFRESH'    IMPORTING        ef_timeout          = lf_timeout    EXCEPTIONS      communication_failure = 1      system_failure            = 2      OTHERS                  = 3.  IF sy-subrc = 0.    IF lf_timeout = abap_false.      SET USER-COMMAND 'ARFC_FUL'.    ELSE.      SET USER-COMMAND 'ARFC_TIM'.    ENDIF.  ENDIF.
ENDFORM.

 

In the PAI I just checked the ok_code value and reloaded the table contents if the 'AFRC_FUL' command was given.

in both cases ('ARFC_FUL' and 'ARFC_TIM'), an new aRFC call should be made. You can see the complete source code in the commit to check this out.

 

The result

We can test it out now. When we delete a row on the dynpro screen, the change should automatically be propagated to the UI5 interface and vice versa.

Capture.JPG

Next week, I will make the same mechanism work between applications hosted on HCP (UI5 apps) and applications hosted on the AS ABAP (UI5 and dynpro). See you next week

Specification of the Push Channel Protocol (PCP)

$
0
0

The Push Channel Protocol is a message based protocol, which is used in context of ABAP Channels, i.e. ABAP Messaging Channels and ABAP Push Channels. The message structure is very similar to a simple HTTP message (see RFC2616) without having the start-line (request-line, response-line) entry. In other words the PCP messages consist of (header) fields and a plain body which is separated by line feeds (LF, i.e. \n – 0x0A (hexadecimal) – 10 (decimal)).  A field entry contains name-value pair separated by “:” character, i.e. <name>:<value>. The names and values are in UTF-8 characters and case-sensitive. The names and values should not contain the special characters LF, “:” and “\”. In that case the special characters must be escaped with the escape character “\” whereas LF is replaced by “\n”. Each field entry, i.e. <name>:<value>, is terminated by a line feed.  The last field entry is completed with double LF, i.e. LFLF, independently of the existence of a body. In case of the existence of a body this content is appended after the double LFs. The body contains only UTF8 characters. A body in binary is encoded in Base64 format. Additionally field name or values must not contain leading or trailing spaces, i.e. white spaces.

 

For the usage of the PCP protocol in context of ABAP Push Channel, i.e. the WebSocket support in ABAP Application Server, the WebSocket subprotocol “v10.pcp.sp.com”, i.e. version 1.0 of the PCP protocol, is to be used.


Augmented Backus-Naur Form (BNF)


A Push Channel Protocol  (PCP) message can be more formally described using the Backus-Naur Form (BNF) grammar used in HTTP/1.1 RFC 2616. Figure 1 shows the structure of a PCP message.


pcp_bnf.gif

Figure 1: BNF grammer of Push Channel Protocol message structure


The WebSocket log of a WebSocket connection to an ABAP Push Channel application using subprotocol “v10.pcp.sap.com” will show following records, when the client sends a PCP message with two fields and a body and the following values.

  • Field 1 consisting of field name “field1” and field value “value1”
  • Field 2 consisting of field name “field2” and field value “field2”
  • A body containing the text “this is the body !”

 

In Chrome developer tools will show the following log entries:

pcp_log_send.gif

with the content:

pcp_log_content_send.gif


In this example the APC application returns the received message including the additional field with the field name “counter” and the field value “1” back:

pcp_log_receive.gif

with the content:

pcp_log_content_receive.gif


PCP Libraries


There exist up to now the following libraries for different use-cases:

  • In ABAP Application Server and in the context of ABAP Channels the class CL_AC_MESSAGE_TYPE_PCP and interface IF_AC_MESSAGE_TYPE_PCP PCP provide the necessary APIs. Furthermore the PCP message type is an inbuilt message type in in ABAP Messaging Channels and in ABAP Push Channels as WebSocket subprotocol "v10.pcp.sap.com".
  • In SAP UI5 the class sap.ui.core.ws.SapPcpWebSocketprovides the necessary PCP methods as an extension to the JavaScript WebSocket class.
  • In other alternative UI technologies, e.g. Web Dynpro, Business Server Pages, SAP GUI HTML, provides “sap-pcp-websocket.js” the necessary PCP methods. This library is available in the mime repository (under the repository path /sap/public/bc/ur) and in ABAP Application Server with 740 SP10. For accessing the library refer to note 2050139 (SAP Push Channel Protocol (PCP) JavaScript library for ABAP Push Channel).

HCP, ABAP and websocket part 4

$
0
0

This is the fourth and final part of my blog post series about HCP - ABAP communication. You can find the first post and the list with other blog posts from these series here: HCP, ABAP and websocket part 1 .

The source code for this blog post is in this commit: 63df829.

 

Today, I will explain how I managed to synchronized the previously built HCP application from week 1 with the ABAP / UI5 / Netweaver application built in weeks 2 and 3. All modifications necessary were on the HCP part (so the on-premise part was untouched).

 

Consuming the same service

First I have modified the UI5 application such that it consumes from the on-premise. This involves exposing the on-premise OData model via the HANA Cloud Connector through a destination.

 

The main difficulty here is that we have a Java application --> we can not use the HTML5 application dispatcher to access on-premise destinations. The solution that I have used is better explained in another blog post of mine: Accessing on-premise from XS on trial.

 

The foundation of this approach lies in the HTTP Proxy, which is a custom class built to act like a proxy (or dispatcher; the concept is loosely based on the RequestDsipatcher Java EE6 class). In my implementation, the proxy itself is implemented as a servlet: the BackendProxy servlet. When making requests to this servlet, it will actually act like the backend oData service.

 

After adding this class, I also adjusted the Component.js file, by adding this new "data source" to our list of possible configurations. See the backendConfig property from below:

 

  serviceConfig : { name: "", serviceUrl: "model.svc/"},  ws: { url: "./ws", cooldown: 1000 },  abapServiceConfig : { name: "", serviceUrl: "/sap/opu/odata/SAP/Z<GW_SERVICE_NAME>/"},  abapWs: { url: "/sap/bc/apc/sap/z<push_channel_name>", cooldown: 1000 },  backendServiceConfig : { name: "", serviceUrl: "backend.svc/"},  backendWs: { url: "./bws", cooldown: 1000},
//...  var p = getQueryParams(document.location.search);  if (p.location == "abap") {       mConfig.serviceConfig = mConfig.abapServiceConfig;       mConfig.ws = mConfig.abapWs;  }  else if (p.location == "backend") {       mConfig.serviceConfig = mConfig.backendServiceConfig;       mConfig.ws = mConfig.backendWs;  }

If we run the app now, with the location=backend URL parameter, the data from the on-premise system should be displayed (but the WS won't work; this is the next step).

Capture.JPG

Delete requests from the HCP UI5 app should also work and should trigger refresh on the dynpro and on-premise UI5, but the reverse won't work (i.e. deleting a row in the dynpro, won't trigger the refresh on the cloud).

 

Synchronizing the WS

Now we just need to synchronize the websocket part, such that the UI5 interface is refreshed when a user deletes a row from the on-premise.

First, we need to build an endpoint. I just simply copied the same endpoint from before into a new class: BackendEndpoint. I did not use the exact same class, because the switch between the pure-HCP version (location empty) and the backed version (location=backend) is done via this "location" parameter on the frontend.

The Endpoint would not know what data each client is actually using (the one stored in the HCP DB or the one from the on-premise). So, to avoid having to send extra information to the WS Endpoint, we just create a new one. I also mapped this new Endpoint to the URL which I have set in the Component.js file ("/bws"). Now the app can be tested again: the WS error should not appear any more, but the synchronization won't work yet.

 

For the synchronization to work, we need to be able to subscribe to the AMC on-premise channel. Because we can not do it directly, an RFC could be the solution. Instead of making a new RFC, I have simply used the same one as in part 3. We need to call this RFC from the Java app, so a destination to the on-premise is also needed.

 

Analogical to the way I synchronized the UI5 and dynpro on-premise apps, I have built a small class which just calls this RFC and sends "refresh" messages to the BackedEndpoint when the RFC calls return (if timeout has not ocured).

 

public enum RFCPoller {  INSTANCE;  private static final Logger LOGGER = LoggerFactory.getLogger(RFCPoller.class);  private RFCPoller() {}  public void start() {  try {       // access the RFC Destination; Replace RFC_DEST with your own destination       JCoDestination destination = JCoDestinationManager.getDestination("REF_DEST");       // make an invocation of STFC_CONNECTION in the backend;       JCoRepository repo = destination.getRepository();       Caller c = new Caller(destination,       repo.getFunction("ZDEMO_FM_REFRESH"));       c.start();  } catch (JCoException e) {       LOGGER.error("JCo Exception: " + e.getMessage());  }
}  private static class Caller extends Thread {  private JCoFunction f;  private JCoDestination d;  public Caller(JCoDestination d, JCoFunction f) {       this.d = d;       this.f = f;  }  @Override  public void run() {       try {            do {                 f.execute(d);                 JCoParameterList exports = f.getExportParameterList();                 String timeout = exports.getString("EF_TIMEOUT");                 if (timeout.trim().length() == 0) {                      BackendEndpoint.sendRefresh();                 }            }while(true);       } catch (JCoException e) {            LOGGER.error("JCo Exception: " + e.getMessage());       }  }
}
}

This Poller class just creates a new thread responsible for calling the RFC in a loop and sending the refresh message to the BackendEndpoint when necessary.

 

Results

Now, I've did the final test:

  • Opened the on-premise dynpro
  • Opened the on-premise UI5 (with location=abap)
  • Opened the HCP UI5 (with location=backend)

When deleting a row from any of these three user interfaces (I've just opened three browser windows and put them next to each other) all the other UIs refresh automatically.

 

As a summary, I have built:

  • a UI5 application which can be deployed to both the cloud and on premise
  • a Java app which can act either as a data source for the UI5 app (via an Olingo OData service) or as a proxy to the backend data; the app is also responsible for the WS communication for on-demand deployed UI5 apps.
  • an AMC + APC for the on-premise application(s)
  • a RFC for wrapping the AMC subscription
  • an OData service build in NW Gateway
  • a dynpro-based report which shows the data in an ALV grid.

In the end, using all these artefacts, I have managed to synchronize real-time an UI5 cloud app, an UI5 on-premise app and a dynpro report.

This concludes my blog post series, I hope you liked it .


Enhancement: How To Use .NET Languages Inside ABAP

$
0
0

Hello community,

 

almost two years ago I presented here a variant how to use Visual Basic inside ABAP. The same example, but with actualized versions, I presented here. Now new versions are available, look here. This was the reason to check and enhance the possibilties to use .NET languages inside ABAP.

 

What are the differences:

  1. Beside the Visual Basic code example I implement also an equivalent C# code example.
  2. The different code parts are now splittet into separate includes. So you can copy and paste your C# or Visual Basic code direct from your local IDE into the ABAP include, without any modifications.
  3. The function to read an include as string is now a method of a local class.
  4. The initialization of the PowerShell frame, via SAPIENS ActiveXPoshV3 library, is now also a method of a local class.
  5. The calls of the .net language methods from ABAP are now very easy and flexible possible from any point of your ABAP code.

 

At first a small visual impression:

001.JPG

 

Here now the code, the first code is the include ZCODEINCLUDE which contains the C# code.

 

//-Begin----------------------------------------------------------------

 

  //-Using--------------------------------------------------------------

    using System;

    using System.Windows.Forms;

 

  //-Namespaces---------------------------------------------------------

    namespace Code {

 

      public class Test {

 

        public static string Hello1() {

          return "Hello World!"

        }

 

        public string Hello2(string Name) {

          return "Hello " + Name + "!"

        }

 

        public void Hello3(string Name) {

          MessageBox.Show("Hello " + Name)

        }

 

      }

    }

 

//-End------------------------------------------------------------------

 

As you can see, it is exact the same example as the Visual Basic code from the posts ealier.

 

Now the PowerShell code, to load the class, from the include ZPSINCLUDE:

 

#-Begin-----------------------------------------------------------------

 

  If (-Not ("Code.Test" -as [type])) {

    #Add-Type -TypeDefinition $Code -Language VisualBasic

    Add-Type -TypeDefinition $Code -Language CSharp

  }

  $Code = New-Object Code.Test

 

#-End-------------------------------------------------------------------

 

Last but not least the ABAP code:

 

"-Begin-----------------------------------------------------------------

  Program zUseDotNet.

 

    "-TypePools---------------------------------------------------------

      Type-Pools OLE2.

 

    "-Constants---------------------------------------------------------

      Constants OUTPUT_CONSOLE Type i Value 0.

      Constants OUTPUT_WINDOW Type i Value 1.

      Constants OUTPUT_BUFFER Type i Value 2.

      Constants CrLf(2) Type c Value cl_abap_char_utilities=>cr_lf.

 

    "-Classes-----------------------------------------------------------

      Class lcl_PoSh Definition.

 

        Public Section.

 

          Class-Methods Init

            Importing i_OutputMode Type i

            Returning Value(r_oPS) Type OLE2_OBJECT.

 

          Class-Methods ReadInclAsString

            Importing i_InclName Type SOBJ_NAME

            Returning Value(r_strIncl) Type String.

 

      EndClass.

 

      Class lcl_PoSh Implementation.

 

        Method Init.

 

          "-Variables---------------------------------------------------

            Data lv_Result Type i.

 

          Create Object r_oPS 'SAPIEN.ActiveXPoSHV3'.

          If sy-subrc = 0 And r_oPS-Handle > 0 And r_oPS-Type = 'OLE2'.

            Call Method Of r_oPS 'Init' = lv_Result Exporting #1 = 0.

            If lv_Result = 0.

              Call Method Of r_oPS 'IsPowerShellInstalled' = lv_Result.

              If lv_Result <> 0.

                Set Property Of r_oPS 'OutputMode' = i_OutputMode.

                Set Property Of r_oPS 'OutputWidth' = 128.

              EndIf.

            Else.

              Free Object r_oPS.

            EndIf.

          Else.

            Free Object r_oPS.

          EndIf.

 

        EndMethod.

 

        Method ReadInclAsstring.

 

          "-Variables---------------------------------------------------

            Data lt_TADIR Type TADIR.

            Data lt_Incl Type Table Of String.

            Data lv_InclLine Type String.

            Data lv_retIncl Type String.

 

          "-Main--------------------------------------------------------

            Select Single * From TADIR Into lt_TADIR

              Where OBJ_NAME = i_InclName.

            If sy-subrc = 0.

              Read Report i_InclName Into lt_Incl.

              If sy-subrc = 0.

                Loop At lt_Incl Into lv_InclLine.

                  lv_retIncl = lv_retIncl && lv_InclLine &&

                    cl_abap_char_utilities=>cr_lf.

                  lv_InclLine = ''.

                EndLoop.

              EndIf.

            EndIf.

            r_strIncl = lv_retIncl.

 

        EndMethod.

 

      EndClass.

 

    "-Variables---------------------------------------------------------

      Data lo_PS Type OLE2_OBJECT.

      Data lv_Result Type String.

      Data lt_Result Type Table Of String.

      Data lv_Code Type String.

      Data lv_PSCode Type String.

      Data lv_PSCodeExec Type String.

      Data lv_strBuf Type String.

 

    "-Main--------------------------------------------------------------

      Start-Of-Selection.

      lo_PS = lcl_PoSh=>Init( OUTPUT_BUFFER ).

      If lo_PS-Handle > 0.

 

        "-Read the dotNET language code from include--------------------

          lv_Code = '$Code = @"' && CrLf &&

            lcl_PoSh=>ReadInclAsString( 'ZCODEINCLUDE' ) &&

            '"@;' && CrLf.

 

        "-Read PowerShell code from include-----------------------------

          lv_PSCode = lv_Code && lcl_PoSh=>ReadInclAsString( 'ZPSINCLUDE' ).

 

        "-Call instance method and view message box---------------------

          lv_PSCodeExec = lv_PSCode && '$Code.Hello3("Stefan")'.

          Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.

 

        "-Call static method and write Hello World into output buffer---

          lv_PSCodeExec = lv_PSCode && '[Code.Test]::Hello1()'.

          Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.

 

        "-Call instance method and write Hello Stefan into output buffer

          lv_PSCodeExec = lv_PSCode && '$Code.Hello2("Stefan")'.

          Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.

 

        "-Catch output buffer into a variable and clear output buffer---

          Call Method Of lo_PS 'OutputString' = lv_Result.

          Call Method Of lo_PS 'ClearOutput'.

 

        "-Write the content of the output buffer------------------------

          Split lv_Result At CrLf Into Table lt_Result.

          Loop At lt_Result Into lv_strBuf.

            Write: / lv_strBuf.

          EndLoop.

 

        Free Object lo_PS.

      EndIf.

 

"-End-------------------------------------------------------------------

 

Let us start an Main comment. At first we initialize the with the method Init the PowerShell environment and set the output buffer as target. The Init method creates an OLE object of SAPIEN.ActiveXPoSHV3, makes some settings and delivers the OLE object. Now we read the .net language code, via the ReadInclAsString method, from the include ZCODEINCLUDE, which contains the C# code. At next we read the PowerShell code from the include ZPSINCLUDE and concatenate it with the .net language code into the variable lv_PSCode. This were the preparations to call now any method from the C# class inside ABAP.

 

Now we can concatenate lv_PSCode with any possible method call and execute it - here I use the variable lv_PSCodeExec.

 

Last but not least we read the output buffer into a variable and clear the output buffer. If you need a result from a method call, you must use this procedure direct after the method call.

 

As you can see, it is very easy to use .NET languages, with all its possibilities of the .net framework, inside ABAP. And if you store the library via BinFile2ABAP on your application server, you can create phantastic solutions without any special requirements to the frontend server - with the exception of the usual standards: Windows, .net framework and PowerShell.

 

Enjoy it.

 

Cheers

Stefan

Enhancement: How To Call DLL Functions In ABAP

$
0
0

Hello community,

 

five years ago I presented here a way how to call DLL functions in ABAP via the DynamicWrapperX library, which is furthermore available here. It is a COM library which maps the DLL functions to COM calls, which are easy usable inside an ABAP program.

Nearly two years ago I presented here a way, how to call DLL functions via SAPIENsCOM library ActiveXPosh.dll. With this library I use PowerShells possibilities to compile .NET languages dynamically at runtime.

Two days ago I presented here an enhancement for an easy using of .NET languages inside ABAP, based on the same method.

Here now an enhancement which combines the knowledge, to show how easy it is to use DLL function calls in ABAP.

 

At first a small architecture map about the using components and their interfaces:

zUseDotNet.jpg

From the ABAP program, which contains a PowerShell script, we call the ActiveX PowerShell library, via the COM interface. The PowerShell script contains the .NET language code, which is compiled at runtime. And the .NET language code stores the interface to the dynamic link library (DLL). On this way it is possible to call a specific function of the DLL from the ABAP code and to work with its results.

 

Here now the code. The first code is the include ZCODEINCLUDE which contains the interface to the DLL:

 

'-Begin-----------------------------------------------------------------

 

  '-Imports-------------------------------------------------------------

    Imports System

    Imports System.Runtime.InteropServices

    Imports Microsoft.VisualBasic

 

  '-Namespaces----------------------------------------------------------

    Namespace Methods

 

      Public Structure TestStruct

        Public x As Integer

        Public y As Integer

      End Structure

 

      Public Class Test

 

        Public Declare Sub TestMethod Lib "Test.dll" _

          Alias "TestMethod" ()

 

        Private Declare Sub pTestMethod2 Lib "Test.dll" _

          Alias "TestMethod2" (strParam1 As IntPtr)

 

        Public Shared Sub TestMethod2(strParam1 As String)

          Dim ptrParam1 As IntPtr = Marshal.StringToHGlobalAuto(strParam1)

          pTestMethod2(ptrParam1)

        End Sub

 

        Private Declare Function pTestMethod3 Lib "Test.dll" _

          Alias "TestMethod3" (strParam1 As IntPtr, intParam2 As Integer) As IntPtr

 

        Public Shared Function TestMethod3(strParam1 As String, intParam2 As Integer) As String

          Dim ptrParam1 As IntPtr = Marshal.StringToHGlobalAuto(strParam1)

          Dim retParam As IntPtr = pTestMethod3(ptrParam1, intParam2)

          Return Marshal.PtrToStringAuto(retParam)

        End Function

 

        Public Declare Function TestMethod4 Lib "Test.dll" _

          Alias "TestMethod4" (fltParam1 As Double) As Double

 

        Public Declare Function TestMethod5 Lib "Test.dll" _

          Alias "TestMethod5" () As Integer

 

        Private Declare Sub pTestMethod6 Lib "Test.dll" _

          Alias "TestMethod6" (StructTest As IntPtr)

 

        Public Shared Sub TestMethod6(StructTest As TestStruct)

          Dim ptrStructTest As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(StructTest))

          Marshal.StructureToPtr(StructTest, ptrStructTest, True)

          pTestMethod6(ptrStructTest)

        End Sub

 

        Private Declare Function pTestMethod7 Lib "Test.dll" _

          Alias "TestMethod7" (X As Integer, Y As Integer) As IntPtr

 

        Public Shared Function TestMethod7(X As Integer, Y As Integer) As TestStruct

          Dim ptrStructTest As IntPtr = pTestMethod7(X, Y)

          Return Marshal.PtrToStructure(ptrStructTest, New TestStruct().GetType)

        End Function

 

      End Class

 

    End Namespace

 

'-End-------------------------------------------------------------------

 

As you can see there are seven external test methods declarations with different interfaces and different parameter types. A few of them are declared as private, but they owns a public wrapper function. These wrapper functions convert the parameter types, e.g. from string to pointer. So it is easier to call the functions.

 


Now the PowerShell code from the include ZPSINCLUDE, to load the .NET language class:

 

#-Begin-----------------------------------------------------------------

 

  If (-Not ("Methods.Test" -as [type])) {

    Add-Type -TypeDefinition $MethodDefinitions -Language VisualBasic > $Null

  }

 

#-End-------------------------------------------------------------------

 


Last but not least the ABAP code:

 

"-Begin-----------------------------------------------------------------

  Program zUseDotNet.

 

    "-TypePools---------------------------------------------------------

      Type-Pools OLE2.

 

    "-Constants--------------------------------------------------------

      Constants OUTPUT_CONSOLE Type i Value 0.

      Constants OUTPUT_WINDOW Type i Value 1.

      Constants OUTPUT_BUFFER Type i Value 2.

      Constants CrLf(2) Type c Value cl_abap_char_utilities=>cr_lf.

 

    "-Classes-----------------------------------------------------------

      Class lcl_PoSh Definition.

 

        Public Section.

 

          Class-Methods Init

            Importing i_OutputMode Type i

            Returning Value(r_oPS) Type OLE2_OBJECT.

 

          Class-Methods Flush.

 

          Class-Methods ReadInclAsString

            Importing i_InclName Type SOBJ_NAME

            Returning Value(r_strIncl) Type String.

 

      EndClass.

 

      Class lcl_PoSh Implementation.

 

        Method Init.

 

          "-Variables---------------------------------------------------

            Data lv_Result Type i.

 

          Create Object r_oPS 'SAPIEN.ActiveXPoSHV3'.

          If sy-subrc = 0 And r_oPS-Handle > 0 And r_oPS-Type = 'OLE2'.

            Call Method Of r_oPS 'Init' = lv_Result Exporting #1 = 0.

            If lv_Result = 0.

              Call Method Of r_oPS 'IsPowerShellInstalled' = lv_Result.

              If lv_Result <> 0.

                Set Property Of r_oPS 'OutputMode' = i_OutputMode.

                Set Property Of r_oPS 'OutputWidth' = 128.

              EndIf.

            Else.

              Free Object r_oPS.

            EndIf.

          Else.

            Free Object r_oPS.

          EndIf.

 

        EndMethod.

 

        Method Flush.

          Call Method CL_GUI_CFW=>Flush.

        EndMethod.

 

        Method ReadInclAsstring.

 

          "-Variables---------------------------------------------------

            Data lt_TADIR Type TADIR.

            Data lt_Incl Type Table Of String.

            Data lv_InclLine Type String.

            Data lv_retIncl Type String.

 

          "-Main--------------------------------------------------------

            Select Single * From TADIR Into lt_TADIR

              Where OBJ_NAME = i_InclName.

            If sy-subrc = 0.

              Read Report i_InclName Into lt_Incl.

              If sy-subrc = 0.

                Loop At lt_Incl Into lv_InclLine.

                  lv_retIncl = lv_retIncl && lv_InclLine &&

                    cl_abap_char_utilities=>cr_lf.

                  lv_InclLine = ''.

                EndLoop.

              EndIf.

            EndIf.

            r_strIncl = lv_retIncl.

 

        EndMethod.

 

      EndClass.

 

    "-Variables---------------------------------------------------------

      Data lo_PS Type OLE2_OBJECT.

      Data lv_Result Type String.

      Data lt_Result Type Table Of String.

      Data lv_Code Type String.

      Data lv_PSCode Type String.

      Data lv_PSCodeExec Type String.

      Data lv_strBuf Type String.

      Data lv_Path Type String.

 

    "-Main--------------------------------------------------------------

      Start-Of-Selection.

      lo_PS = lcl_PoSh=>Init( OUTPUT_BUFFER ).

      If lo_PS-Handle > 0.

 

        "-Add path to the libray to environment path--------------------

          lv_PSCodeExec = '$EnvPath = $env:PATH;' &&

            '$env:PATH += ";C:\Dummy";$EnvPath'.

          Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.

          lcl_PoSh=>Flush( ).

          Call Method Of lo_PS 'OutputString' = lv_Result.

          Call Method Of lo_PS 'ClearOutput'.

          lcl_PoSh=>Flush( ).

          lv_Path = lv_Result.

 

        "-Read the dotNET language code from include--------------------

          lv_Code = '$MethodDefinitions = @"' && CrLf &&

            lcl_PoSh=>ReadInclAsString( 'ZCODEINCLUDE' ) &&

            '"@;' && CrLf.

 

        "-Read PowerShell code from include-----------------------------

          lv_PSCode = lv_Code && lcl_PoSh=>ReadInclAsString( 'ZPSINCLUDE' ).

 

        "-Call the different functions of the library-------------------

          lv_PSCodeExec = lv_PSCode && '[Methods.Test]::TestMethod()'.

          Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.

          lcl_PoSh=>Flush( ).

 

          lv_PSCodeExec = lv_PSCode &&

            '[Methods.Test]::TestMethod2("Hallo Welt")'.

          Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.

          lcl_PoSh=>Flush( ).

 

          lv_PSCodeExec = lv_PSCode &&

            '$strRc = [Methods.Test]::TestMethod3("Hallo Welt", 4711)'.

          lv_PSCodeExec = lv_PSCodeExec && CrLf && 'Write-Host $strRc'.

          Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.

          lcl_PoSh=>Flush( ).

 

          lv_PSCodeExec = lv_PSCode &&

            '$fltRc = [Methods.Test]::TestMethod4(3.14159267)'.

          lv_PSCodeExec = lv_PSCodeExec && CrLf && 'Write-Host $fltRc'.

          Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.

          lcl_PoSh=>Flush( ).

 

          lv_PSCodeExec = lv_PSCode &&

            '$intRc = [Methods.Test]::TestMethod5()'.

          lv_PSCodeExec = lv_PSCodeExec && CrLf && 'Write-Host $intRc'.

          Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.

          lcl_PoSh=>Flush( ).

 

          lv_PSCodeExec = lv_PSCode &&

            '$TestStruct = New-Object Methods.TestStruct'.

          lv_PSCodeExec = lv_PSCodeExec && CrLf && '$TestStruct.x = 4'.

          lv_PSCodeExec = lv_PSCodeExec && CrLf && '$TestStruct.y = 2'.

          lv_PSCodeExec = lv_PSCodeExec && CrLf &&

            '[Methods.Test]::TestMethod6($TestStruct)'.

          Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.

          lcl_PoSh=>Flush( ).

 

          lv_PSCodeExec = lv_PSCode &&

            '$rcStruct = [Methods.Test]::TestMethod7(1606, 1964)'.

          lv_PSCodeExec = lv_PSCodeExec && CrLf &&

            'Write-Host $rcStruct.x " : " $rcStruct.y'.

          Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.

          lcl_PoSh=>Flush( ).

 

        "-Catch output buffer into a variable and clear output buffer---

          Call Method Of lo_PS 'OutputString' = lv_Result.

          Call Method Of lo_PS 'ClearOutput'.

          lcl_PoSh=>Flush( ).

 

        "-Write the content of the output buffer------------------------

          Split lv_Result At CrLf Into Table lt_Result.

          Loop At lt_Result Into lv_strBuf.

            Write: / lv_strBuf.

          EndLoop.

 

        "-Set environment path back-------------------------------------

          lv_PSCodeExec = '$env:PATH = ' && lv_Path.

          Call Method Of lo_PS 'Execute' Exporting #1 = lv_PSCodeExec.

 

        Free Object lo_PS.

      EndIf.

 

"-End-------------------------------------------------------------------

 

In comparison with my example here, I added the method flush to the local class.

If you look at the section "Add path to the library to environment path" you will see the method how to call a PowerShell command, to get the result back and to work with it.

 

The calls of the DLL functions are also easily:

  1. TestMethod is only a sub (function with result void), without any parameters.
  2. TestMethod2 is also a sub, with a string parameter.
  3. TestMethod3 is a function, with a string, an integer parameter and a string result.
  4. TestMethod4 is a function, with a float parameter and result.
  5. TestMethod5 is a function, with no parameters and an integer result.
  6. TestMethod6 is a sub, with a structure parameter.
  7. TestMethod7 is a function, with a structure result.

 

As you can see my example uses many different types of parameters and results. All what you can do with a .NET language, you can do inside ABAP.

 

 

Here now the code of the dynamic link library (DLL), to show the receiver of the calls. You can use any program language you want to build a DLL, my example library was programmed in FreeBasic language.

 

'-Begin-----------------------------------------------------------------

 

  '-Includes------------------------------------------------------------

    #Include Once "windows.bi"

 

  '-Structures----------------------------------------------------------

    Type TestStruct

      x As Integer

      y As Integer

    End Type

 

  '-Variables-----------------------------------------------------------

    Dim Shared struct As TestStruct

 

  Extern "Windows-MS"

 

  '-Sub TestMethod------------------------------------------------------

    Sub TestMethod Alias "TestMethod" () Export

      MessageBox(NULL, "Dies ist ein Test", "Test", MB_OK)

    End Sub

 

  '-Sub TestMethod2-----------------------------------------------------

    Sub TestMethod2 Alias "TestMethod2" _

      (ByVal strParam1 As WString Ptr) Export

      MessageBox(NULL, Str(*strParam1), "Test2", MB_OK)

    End Sub

 

  '-Function TestMethod3------------------------------------------------

    Function TestMethod3 Alias "TestMethod3" _

      (ByVal strParam1 As WString Ptr, ByVal intParam2 As Integer) _

      As WString Ptr Export

      MessageBox(NULL, Str(*strParam1) & " " & Str(intParam2), _

        "Test3", MB_OK)

      TestMethod3 = @WStr("Hallo zusammen")

    End Function

 

  '-Function TestMethod4------------------------------------------------

    Function TestMethod4 Alias "TestMethod4" _

      (ByVal fltParam1 As Double) As Double Export

      Dim rcValue As Double

      rcValue = fltParam1 * 2

      MessageBox(NULL, Str(fltParam1) & " * 2 = " & Str(rcValue), _

        "Test4", MB_OK)

      TestMethod4 = rcValue

    End Function

 

  '-Function TestMethod5------------------------------------------------

    Function TestMethod5 Alias "TestMethod5" () As Integer Export

      TestMethod5 = 16061964

    End Function

 

  '-Sub TestMethod6-----------------------------------------------------

    Sub TestMethod6(ByVal structParam1 As TestStruct Ptr) Export

      MessageBox(NULL, Str(structParam1->x) & " " & _

        Str(structParam1->y), "Test6", MB_OK)

    End Sub

 

  '-Function TestMethod7------------------------------------------------

    Function TestMethod7(ByVal x As Integer, ByVal y As Integer) _

      As TestStruct Ptr Export

      struct.x = x

      struct.y = y

      TestMethod7 = @struct

    End Function

   

  End Extern

 

'-End-------------------------------------------------------------------

 

Enjoy it.

 

Cheers

Stefan

ABAP Push / Messaging Channel and SAPUI5 Demo Application

$
0
0

Based on the Blog series published by Masoud Aghadavoodi Jolfaei:

 

ABAP Channels Part 1: WebSocket Communication Using ABAP Push Channels

ABAP Channels Part 2: Publish/Subscribe Messaging Using ABAP Messaging Channels

ABAP Channels Part 3: Collaboration Scenario Using ABAP Messaging and ABAP Push Channels

Specification of the Push Channel Protocol (PCP)

 

I've created an SAPUI5 Demo application using the ABAP Push Channel (APC, WebSockets) and ABAP Messaging Channel (AMC). To benefit from the WebSocket technology in your browser please check the support matrix at https://en.wikipedia.org/wiki/WebSocket#Browser_implementation. At the ABAP Backend I've used a NetWeaver ABAP Application Server 7.40 SP8.

 

ABAP Push Channel

 

Let's define the ABAP Push Channel first. Check out the above mentioned Blog's for details:

APC-ZAMC_ECHO.PNG

 

Here's the source code of the class Implementation:

 

CLASS zcl_apc_wsp_ext_zapc_echo DEFINITION  PUBLIC  INHERITING FROM cl_apc_wsp_ext_stateless_pcp_b  FINAL  CREATE PUBLIC .  PUBLIC SECTION.    METHODS if_apc_wsp_ext_pcp~on_message         REDEFINITION .    METHODS if_apc_wsp_ext_pcp~on_start         REDEFINITION .  PROTECTED SECTION.  PRIVATE SECTION.    CLASS-METHODS prepare_message_for_ui      IMPORTING        !iv_text       TYPE string      RETURNING        VALUE(rv_text) TYPE string .
ENDCLASS.
CLASS zcl_apc_wsp_ext_zapc_echo IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_APC_WSP_EXT_ZAPC_ECHO->IF_APC_WSP_EXT_PCP~ON_MESSAGE
* +-------------------------------------------------------------------------------------------------+
* | [--->] I_MESSAGE                      TYPE REF TO IF_AC_MESSAGE_TYPE_PCP
* | [--->] I_MESSAGE_MANAGER              TYPE REF TO IF_APC_WSP_MESSAGE_MANAGER_PCP
* | [--->] I_CONTEXT                      TYPE REF TO IF_APC_WSP_SERVER_CONTEXT
* +--------------------------------------------------------------------------------------</SIGNATURE>  METHOD if_apc_wsp_ext_pcp~on_message.    DATA: lo_producer TYPE REF TO cl_amc_message_type_pcp.    TRY.
* retrieve the text message        DATA(lv_text) = i_message->get_text( ).        lo_producer ?= cl_amc_channel_manager=>create_message_producer(          i_application_id = 'ZAMC_ECHO'          i_channel_id     = '/echo' ).        lv_text = prepare_message_for_ui( lv_text ).        DATA(lo_msg) = cl_ac_message_type_pcp=>create( ).        lo_msg->set_text( i_message = lv_text ).        lo_producer->send( i_message = lo_msg ).      CATCH cx_ac_message_type_pcp_error INTO DATA(lx_pcp_error).        MESSAGE lx_pcp_error->get_text( ) TYPE 'E'.      CATCH cx_amc_error INTO DATA(lx_amc_error).        MESSAGE lx_amc_error->get_text( ) TYPE 'E'.      CATCH cx_apc_error INTO DATA(lx_apc_error).        MESSAGE lx_apc_error->get_text( ) TYPE 'E'.    ENDTRY.  ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_APC_WSP_EXT_ZAPC_ECHO->IF_APC_WSP_EXT_PCP~ON_START
* +-------------------------------------------------------------------------------------------------+
* | [--->] I_CONTEXT                      TYPE REF TO IF_APC_WSP_SERVER_CONTEXT
* | [--->] I_MESSAGE_MANAGER              TYPE REF TO IF_APC_WSP_MESSAGE_MANAGER_PCP
* +--------------------------------------------------------------------------------------</SIGNATURE>  METHOD if_apc_wsp_ext_pcp~on_start.    TRY.
* bind the WebSocket connection to the AMC channel        DATA(lo_binding) = i_context->get_binding_manager( ).        lo_binding->bind_amc_message_consumer(          i_application_id = 'ZAMC_ECHO'          i_channel_id     = '/echo' ).      CATCH cx_apc_error INTO DATA(lx_apc_error).        DATA(lv_message) = lx_apc_error->get_text( ).        MESSAGE lx_apc_error->get_text( ) TYPE 'E'.    ENDTRY.  ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_APC_WSP_EXT_ZAPC_ECHO=>PREPARE_MESSAGE_FOR_UI
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_TEXT                        TYPE        STRING
* | [<-()] RV_TEXT                        TYPE        STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>  METHOD prepare_message_for_ui.    TYPES: BEGIN OF t_message,             text TYPE string,             user TYPE uname,             date TYPE timestamp,           END OF t_message.    DATA: ls_message TYPE t_message.    ls_message-text = iv_text.    ls_message-user = sy-uname.    GET TIME STAMP FIELD ls_message-date.    DATA(lo_json_witer) = cl_sxml_string_writer=>create(                            type = if_sxml=>co_xt_json                          ).    CALL TRANSFORMATION id SOURCE ls_message = ls_message                        RESULT XML lo_json_witer.    DATA(lv_xstr) = lo_json_witer->get_output( ).    CALL FUNCTION 'ECATT_CONV_XSTRING_TO_STRING'      EXPORTING        im_xstring = lv_xstr
*       im_encoding = 'UTF-8'      IMPORTING        ex_string  = rv_text.  ENDMETHOD.
ENDCLASS.

 

ABAP Messaging Channel

 

For the ABAP Messaging Channel it is important to define the Authorized Programs:

AMC-ZAMC_ECHO.PNG

 

HCP Git to GitHub

 

This is just to document how to bring an app developed in SAP Web IDE from the HCP Trial Git repository to GitHub. I've used the following steps adopted from the documentation:

 

Adding an existing project to GitHub using the command line - User Documentation


First of all clone the HCP Git repository to the local system and switch to the new folder:

 

git clone https://<HCP Username>@git.hanatrial.ondemand.com/<HCP Accountname>/<HCP GIT Repository Name>
cd apcecho/

Now create a new repository on GitHub and add this repository. I've named it "github":

 

git remote add github https://github.com/gregorwolf/apcecho.git

As I've created some files (Readme, License) in the GitHub Repository this files have to be pulled from the repository:

 

git pull github

And merged with the master:

 

git merge github/master

Then push the changes to both repositories:

 

git push github master
git push origin master

 

 

SAPUI5 Frontend

 

I've used the SAPUI5 Application Template in SAP Web IDE to bootstrap my app. You can get the source at:


gregorwolf/apcecho


You can use the Project SAPUI5-Deployer by Graham Robinson to import this Git repository directly into your ABAP stack.


That's for the moment. Looking forward for your feedback.

AutoIt Runtime Environment for ABAP

$
0
0

Hello community,

 

often I have the requirement of a closer cooperation between ABAP on the application server and processes and windows on the frontend server. To counter that requirement I integrate AutoIt, a very powerful Windows scripting language, as a runtime environment seamlessly in ABAP. The controlling and communication between ABAP and AutoIt is realized in a wrapper class. All necessary modules are available in the context of ABAP on the application server, even the AutoIt scripts. You need no prerequisites on the frontend server - exception is SAP GUI for Windows. On this way is it now very easy possible to to combine the possibilities of the frontend and the backend server.

 

You can find more information and all sources at github here.

 

The following architecture map shows the relations between the different modules.

AutoItRTE4ABAP.jpg

 

Enjoy it.

 

Cheers

Stefan

How to Restart SAP Logon From an ABAP Program

$
0
0

Hello community,

 

one month ago I present here the possibility to use the Scripting Language AutoIt via a runtime environment (RTE) from the application server. Here now an example how to restart the SAP Logon on the presentation server from an ABAP program and after the restart the ABAP program will execute again.

 

Here the ABAP program which provides the AutoItRTE on the frontend server and starts the script.

 

"-Begin------------------------------------------------------------------

   Program ZRESTARTSAPLOGON.

 

     "-Variables---------------------------------------------------------

       Data lo_AutoIt Type Ref To ZCL_AUTOIT.

       Data lv_WorkDir Type String.

       Data lv_Str Type String.

 

     "-Main--------------------------------------------------------------

       Try.

         Create Object lo_AutoIt.

         lv_Str = lo_AutoIt->GetClipBoard( ).

         If lv_Str <> 'Restart SAP Logon successful'.

           lo_AutoIt->ProvideRTE( i_DeleteExistRTE = ABAP_FALSE ).

           lv_WorkDir = lo_AutoIt->GETWORKDIR( ).

           lo_AutoIt->StoreInclAsFile( i_InclName = 'ZRESTARTSAPLOGONAU3'

             i_FileName = lv_WorkDir && '\RestartSAPLogon.au3').

           lo_AutoIt->Execute( i_FileName = 'RestartSAPLogon.au3'

             i_WorkDir = lv_WorkDir ).

         EndIf.

         lo_AutoIt->PutClipBoard( '' ).

 

         Write: / 'Restart successful'.

 

       Catch cx_root.

         Write: / 'An error occured'.

       EndTry.

 

"-End--------------------------------------------------------------------

 

Here the AutoIt script which restarts the SAP Logon, reconnects the application server and restart the ABAP program. The most steps uses SAP GUI Scripting API.

 

;-Begin------------------------------------------------------------------

 

   ;-Sub RestartProcess--------------------------------------------------

     Func RestartProcess($ProcessName)

 

       $PID = ProcessExists($ProcessName)

       If $PID Then

 

         $oWMI = ObjGet("winmgmts:\\.\root\CIMV2")

         If IsObj($oWMI) Then

           $Process = $oWMI.ExecQuery("Select * From win32_process " & _

             "Where Name = '" & $ProcessName & "'")

           If IsObj($Process) Then

             $ProcessPath = $Process.ItemIndex(0).ExecutablePath

           EndIf

         EndIf

 

         ProcessClose($PID)

         ProcessWait($PID)

 

         Run($ProcessPath)

 

       EndIf

 

     EndFunc

 

   ;-Sub Main------------------------------------------------------------

     Func Main()

 

       RestartProcess("saplogon.exe")

       WinWait("SAP Logon ")

       Sleep(4096)

       ClipPut("Restart SAP Logon successful")

 

       $SAPROT = ObjCreate("SapROTWr.SAPROTWrapper")

       If Not IsObj($SAPROT) Then

         Exit

       EndIf

 

       $SapGuiAuto = $SAPROT.GetROTEntry("SAPGUI")

       If Not IsObj($SapGuiAuto) Then

         Exit

       EndIf

 

       $application = $SapGuiAuto.GetScriptingEngine()

       If Not IsObj($application) Then

         Exit

       EndIf

 

       $connection = $application.Openconnection("NSP", True)

       If Not IsObj($connection) Then

         Exit

       EndIf

 

       $session = $connection.Children(0)

       If Not IsObj($session) Then

         Exit

       EndIf

 

       $session.findById("wnd[0]/usr/txtRSYST-BNAME").Text = "BCUSER"

       $session.findById("wnd[0]/usr/pwdRSYST-BCODE").Text = InputBox("Password", "Enter your password", "", "*")

       $session.findById("wnd[0]").sendVKey(0)

       $session.findById("wnd[0]/tbar[0]/okcd").text = "/nse38"

       $session.findById("wnd[0]").sendVKey(0)

       $session.findById("wnd[0]/usr/ctxtRS38M-PROGRAMM").text = "ZRESTARTSAPLOGON"

       $session.findById("wnd[0]").sendVKey(8)

 

     EndFunc

 

   ;-Main----------------------------------------------------------------

     Main()

 

;-End--------------------------------------------------------------------

 

The interprocess communication between the AutoIt script and the ABAP program is via clipboard. All necessary modules are available on the application server. As you can see, with the combination of ABAP, AutoIt and SAP GUI Scripting you can create amazing solutions.

 

Enjoy it.

 

Cheers

Stefan

Viewing all 68 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>