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

How to play around with SAP and Crystal reports, trial versions only

$
0
0
Note: This blog is assigned into the ABAP category as well, because my background is ABAP and I expect other SAP/ ABAP people to get interested in Crystal Reports and other BO stuff.

There are many people very curious about the "new" toys in the SAP world (well, Crystal Reports are not very new, but they are for the people working with SAP/ ABAP like me. We didn´t care before SAP acquired BusinessObjects). Or there are people who use the spare time to widen the skills. These people typically don´t have access to any support in their companies and they explore the place "where no one has gone before" to prove the new things, that can be (maybe one day) beneficial for their companies (and of course for them in their career perspectives). I am one of those guys.

I wanted to try Crystal Reports (CRs) integration with SAP ERP and didn´t have any support, any relevant (BO) experience or background. But I wanted to make this work - satisfy my curiosity. I write this blog to share the experience with the other cowboys exploring the Wild West and maybe I will inspire somebody to write some similar dummy-friendly blog about some other interesting topic.

What we gonna need

Goal: Use Crystal Reports trial together with SAP NetWeaver (ERP) (trial), create (or just run, you will see) a CR based on the SAP (table) data.

What will you need to re-build the "solution" of mine?

All this software is available for free download as a trial version. As far as I can tell you don´t have to pay a cent to re-create my demo landscape.

Note: Probably this blog states facts, which are already well known and maybe even copies some other resources about the CRs. I didn´t know any of the resources when trying to run the whole scenario and don´t know any of them at the time I write this blog. The target audience for this blog is anybody who has absolutely NO experience with BO and has at least some experience with SAP ERP/ ABAP and this kind of stuff (we, SAP people, start to explore the BO portfolio).

How to make this work

Ok, let´s do this. Follow the instructions:

  • Install the virtualization software (Virtualbox in my case)
  • Convert the OS media to the format acceptable by the virtualization software as a disk image (I used Virtual Box, I had to convert the OS images to ISO format), change the boot media to the DVD/ disk image (where disk 1 of OS install is loaded)
  • Install the OS (as I mentioned above, I have installed Server 2003)
  • Install the guest additions for your virtualization software (it helps a lot!)
  • setup the shared folder between the host and the guest machine (in the virtual machine you can access the folder through the Network)
  • Install the CR viewer, Install the Adobe Reader (helps a lot to read the documentation)
  • Register on the provided SAP BO page, get the CR trial link, download the software
  • Install the software with the code form your email (you get the email, didn´t you?)
  • Install the integration kit for SAP, provide the code from the download page (oh, yes, you can get the code without the registration here, read the page!!)
  • Run the Crystal Reports (Designer). Read the first page. You will understand what various sources of information you can use. Note that you can download the demo CRs to get a running solution with minimum effort.
  • I have downloaded the samples for the SAP NetWeaver integration and one of the files was a CR with data source: EKKO, EKPO, LFA1. I hope you start to like the CRs when you can see something familiar.
  • The last problem to have your own data in the CR is the connection. The samples use some sample/ testing connection which you would probably like to change to you system. To be able to use your SAP connection, you must have SAP GUI installed and the system, you would like to access from the CR, configured in the SAP GUI systems list.
  • In the CR designer, with the sample (which connects t the ERP system) opened, open the Database Expert. Check the list: My connections, New connection. Under the new connection there are some SAP relevant items to check.
  • Here you may wish to install your SAP NetWeaver trial if you don´t have access to any SAP NetWeaver sandbox system to play with.
  • Create a new SAP connection (the one where you can use DB tables):  first pick a SAP system from the list. If you cannot see any SAP system in the list, you didn't configure one in your SAP GUI. Next provide the access information. Of course keep the security in mind. If you have to access the VPN or something to "see" your NetWeaver system, jump into the VPN first. Now you can replace the sample´s connection with your own.
  • You should be able to see your own SAP data in the demo Crystal Report.

I hope the whole scenario works for you now. I am sorry if some parts of the "TODO" list are not described in the greater detail or maybe even an action is missing. It took me few weeks to create a working demo myself and it is possible I didn´t mention all the necessary steps here.

In that case Google can help or I will try to help (in this order, Google first, friends) everybody to make the scenario work and will update the list if anybody will find any error or a missing action.

Useful sources of information:

I hope this how-to blog will help few people to start playing around with the Crystal Reports (and of course help SAP get some new CR customers). If you would like to add or correct anything regarding this blog, please contact me, we will work on it. Regards Otto


Twibap: the ABAP Twitter API

$
0
0

Prolog

You probably know my last year's SCN Blog post A story about Twitter, XML and WD4A ;-) In April this year I've received a short DM from Mark F.  (http://www.sdn.sap.com/irj/scn/bc?u=g9g0pzynhca%3d) "getting #SAP #ABAP on this list would be wholesome" (he referted to the site: http://dev.twitter.com/pages/libraries  (http://dev.twitter.com/pages/libraries)).

Nice idea, I thought, give me time until the summer break.  End of July I wanted to refresh my Twitter API knowledge by reading the docs and I saw this message on the dev site: "The @twitterapi team will be shutting off basic authentication on the Twitter API. All applications, by this date  (http://countdowntooauth.com/), need to switch to using OAuth."  No problem, with OAuth I've battled already while developing Wave and Streamwork apps. (haha, more later)

Chapter 1 - The Twitter API

The Twitter REST(?) API is pretty good described on http://dev.twitter.com/doc  (http://dev.twitter.com/doc) I think, no further explanation is needed here.  

Chapter 2 -  The JSON Parser

In my Twitter WDA client I've used the XML response. Since I felt in love with JSON  (http://json.org/) while working with Python, I've decided to use JSON this time. First prob: how to parse JSON ABAP?  My search on SCN found the nice JSON function group from Quentin Dubois  (http://www.sdn.sap.com/irj/scn/bc?u=z5uinayahfg%3d) (Wiki page  (http://wiki.sdn.sap.com/wiki/pages/viewpage.action?pageId=163840922))

Because the Twitter response contains not only flat data but also embedded objects (a status object always contains a user object) and some responses are arrays, the mentioned function group is not really the solution I needed, so I've decided to write my own parser (but with parts of the code of the module, hope Quentin doesn't kill me now).  The result of a parsed JSON object is now a hashed key/value table, where we can read each element by simply call e.g. "text = simplejson->get_value( 'text' ).".

Is the result of the read element again an object, you have just to parse it again:

user = simplejson->get_value( 'user' ).       "returns another object 
user_data = simplejson->parse_object( user ). "parse object 
simplejson->set_data( user_data ).            "set new data in parser 
screen_name = simplejson->get_value( 'screen_name' ).  "get element 

 

The result of a parsed JSON array is a standard table of the hashed key/value table.  With this tiny simplejson helper class I wrote my first twitter API classes, and since the basic authentification is not cut off yet, all test went well until here. 

Chapter 3 - OAuth

The next step of the journey was the implementation of OAuth. With a look at my Python sources (Streamwork OAuth implementation) and the first chapters of the Twitter OAuth docs  (http://dev.twitter.com/pages/auth) it seemed very familiar and I began with the realization.

An OAuth request contains, amongst others, two fields called "oauth_nonce", a string with random characters, and "oauth_timestamp", the seconds counted from Jan. 1st 1970. Because there are no standard functions (I think so), I've developed two small helper methods: 

method CREATE_NONCE.            CONSTANTS: chars TYPE string                                                       VALUE '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz'.            DATA: rnd TYPE i.            DO length TIMES.   "input parameter (twitter needs 8 chars)                        CALL FUNCTION 'QF05_RANDOM_INTEGER'                                    EXPORTING                                                ran_int_max = 60                                                ran_int_min = 0                                    IMPORTING                                                ran_int     = rnd.                        CONCATENATE                                    nonce                                    chars+rnd(1)                        INTO nonce.            ENDDO.

endmethod.


method CREATE_TIMESTAMP.            CONSTANTS: unix TYPE d VALUE '19700101'. "Unix' birthday            DATA: timestamp_i TYPE i                    , diff TYPE i                          .            GET TIME.            diff = sy-tzone.   "diff to UTC in secs            IF sy-dayst = 'X'. "daylight saving active?                        ADD 3600 TO diff.            ENDIF.            timestamp_i = ( sy-datum - unix ) * 86400   "days in secs                                    +   sy-uzeit(2)       * 3600    "hours in secs              +                                        sy-uzeit+2(2)     * 60      "mins in secs                                                      +   sy-uzeit+4(2)               "secs                                                      -   diff                        "diff to UTC in secs                                                      .            timestamp = timestamp_i.            timestamp = timestamp(10). "w/o sign (trailing space)

endmethod.

 

Hint under friends: if the timestamp is not correct, Twitter will refuse your request, believe me
-> set your system time correctly! 
-> read more: OAuth at Twitter  (
http://dev.twitter.com/pages/auth

Chapter 4 - HMAC-SHA1 

But the first test results brought me back down to earth: I've overseen the tiny remark "Twitter requires that all OAuth requests be signed using the HMAC-SHA1 algorithm." WTF? Streamwork uses PLAINTEXT authentification, but what is HMAC-SHA1? Googlegooglegoogle  The search brought me two results (ok, much more than two, but these two are the most relevant ones): 

An SHA1 function module? Great. Looking into the source of FM "CALCULATE_HASH_FOR_CHAR" and the question marks in my brain appeared again (only a system-call in it). What does the FM docu say? Nothing, no docu available. The usage of this FM was definitely too "hot" for me. What, if I overwrote some needed cryptographic stuff in the system. Not fatal on my own systems, but what about client systems? No, thanks. Fortunately I remembered, that I've read somewhere somewhat about the usage of Javascript within ABAP.

SE24, "CL_*JAVA*",  points me to this SAP help site (http://help.sap.com/saphelp_nw70ehp1/helpdata/en/49/a8e3c8d59811d4b2e90050dadfb92b/frameset.htm

Again WTF....   But thanks god (and SAP!), we still have the old docus available: here you can find the relevant part from NW7.0  (http://help.sap.com/saphelp_nw70/helpdata/en/86/8676416e805958e10000000a1550b0/frameset.htm)  

Chapter 5 - Javascript

Although I don't like Javascript very much, playing around with the CL_JAVA_SCRIPT class, I was surprised about the functionality of the class. Even whole ABAP-OO classes can be bound the Javascript source. A CL_PYTHON would definitely be better, but the class works great atm and is probably the only way to use open source libraries for functions not delivered by SAP! 

Back to topic: my first experiments with the class I've done like described in the docu: with inline code. But for sure, this is not the solution I want to build into the API. Where to store the Javascript sources? Where they belong: in the Mime repository. Now we have the SHA1 library and an additional single liner called twibap.js stored in the mime repository and with this code snipped we can load the source back into an ABAP string:

*--- load Javascript sources from mime repository ---*   
mime_api = cl_mime_repository_api=>get_api( ).    
mime_url  = '/SAP/PUBLIC/BC/ztwibap/sha1.js'.    
mime_api->get(               EXPORTING                           i_url = mime_url               IMPORTING                           e_content = mime_content                           ).    

CALL FUNCTION 'ECATT_CONV_XSTRING_TO_STRING'               EXPORTING                           im_xstring = mime_content               IMPORTING                           ex_string  = sha1_source.    

mime_url  = '/SAP/PUBLIC/BC/ztwibap/twibap.js'.    
mime_api->get(               EXPORTING                           i_url = mime_url               IMPORTING                           e_content = mime_content                           ).    

CALL FUNCTION 'ECATT_CONV_XSTRING_TO_STRING'               EXPORTING                           im_xstring = mime_content               IMPORTING                           ex_string  = twibap_source.    

CONCATENATE               sha1_source               twibap_source   
INTO js_source SEPARATED BY cl_abap_char_utilities=>cr_lf.  

 

 

 

twibap.js contains:

abap.oauth_signature = b64_hmac_sha1(abap.oauth_secret, abap.basestring) + '=';  

and with this code we finally can sign the message:

*--- compile source and bind variables ---*   
js_processor = cl_java_script=>create( ).    
js_processor->bind(               EXPORTING                           name_obj  = 'abap'                           name_prop = 'oauth_secret'               CHANGING                           data      = me->oauth_secret                           ).    


basestring = create_basestring( method ).    
js_processor->bind(               EXPORTING                           name_obj  = 'abap'                           name_prop = 'basestring'               CHANGING                           data      = basestring                           ).    

js_processor->bind(               EXPORTING                           name_obj  = 'abap'                           name_prop = 'oauth_signature'               CHANGING                           data      = me->oauth_signature                           ).    

return_value = js_processor->evaluate( js_source ). 

 

 

 

In addition I only had to develop my own encoding method called “percent_encode”, because the "cl_http_utility=>escape_url()" method doesn't fit to the OAuth dictate, where the only characters you can ignore are "- _ . ~" (and some other abnormalities). 

The whole Twitter workflow works nice now, but I was not very satisfied with this JS solution. Therefore back to SAP notes and google for a deeper search for more information about the function module "CALCULATE_HASH_FOR_CHAR".  

Chapter 6 - The SecureStore

In Note 1416202 I finally found the answer. The function modules are NOT "secret", but "The raw documentation was not activated." With NW 7.01 SP7 the documentation will be delivered (I was so close to install SP7 on my system..., but luckily I found the docu in the infinite vastness of the internet). 

In the documentation of the function group "SECH" and its function modules we can read, that we can use these function modules for our own purposes. So did I:  

"*--- set secret to SecureStorage ---*   
CALL FUNCTION 'SET_HMAC_KEY'               EXPORTING                           keycstr            = me->oauth_secret                           client_independent = space.    

"*--- calculate base64 signature ---*   
CALL FUNCTION 'CALCULATE_HMAC_FOR_CHAR'               EXPORTING                           alg            = 'SHA1'                           data           = basestring                           key_must_exist = 'X'               IMPORTING                           hmacbase64     = signature. 

 

Hey, it works! Party! Trashed the Javascript part. 

Boom, Dump, failed again. What happened? In the first steps of the OAuth authorization process (request_token etc.) the oauth_secret contains only the consumer_secret (42 characters + "&"). The function module 'SET_HMAC_KEY' works brilliant until that point, where I want to sign a user action (e.g. sending a tweet). In this case the secret combines the consumer secret and the token secret (of the user). The function module responses with an "parameter_length" exception.

With some experiments I found out, that the FM only accepts 81 characters as maximum.

Hey, why? I only want to SET the key, no process at this moment. And in addition: nowhere in the HMAC-SHA1 OAuth key definition is a length maximum mentioned.  In my despair I opened a SCN forum thread  (HMAC (SHA1) key longer than 81 characters not possible?).  And what a surprise (or not): no 24 hours later I've got the solution SCN members rock! 

The solution: if the key is longer than 81 characters, we have to store the hash of the key, not the key itself (still don’t know why).

The code snippet:  

"*--- FM 'SET_HMAC_KEY' doesn't accept keys > 81 chars ---*   
IF STRLEN( me->oauth_secret ) < 82.                "*--- set secret to SecureStorage ---*               CALL FUNCTION 'SET_HMAC_KEY'                           EXPORTING                                       keycstr            = me->oauth_secret                                       client_independent = space.    
ELSE.                "*--- hash the secret ---*               CALL FUNCTION 'CALCULATE_HASH_FOR_CHAR'                           EXPORTING                                       data  = me->oauth_secret                           IMPORTING                                       hashx = hashx.                secret_hashed = hashx.                "*--- set hashed secret to SecureStorage ---*               CALL FUNCTION 'SET_HMAC_KEY'                           EXPORTING                                       keyxstr            = secret_hashed                                       client_independent = space.    

ENDIF.

 

 

Thanks to my decades long experience I've only stared out the Javascript part. Now I only had to activate the part again, create a nugget especially for 7.00 systems and include the Mime objects into the nugget again. 

Epilog

You: "And for what is it good for?"
Me: "No idea"
You: "But why did you make it"
Me: "You could also ask: Why are you running Android 2.2 (Froyo) on a WindowsMobile phone. The answer would be the same:

"Because it works, and it makes so much fun "

 

G+

BTEs - Business Transaction Events

$
0
0

Business Transaction Events (BTEs) have been available as of Release 4.0 and are another technique for implementing program exits, and only program exits.

BTEs are generally found in the general ledger accounting (FI-GL), account receivable and payable (FI-AR and FI-AP) and sales and distribution components.

 

A BTE has a predefined interface and allows you to attach additional functionality in the form of a service funtion module.You must create funtion module with the parameters you have previously dictated. The BTE is called by the SAP standard program by a call to function OPEN_FI_PERFORM_ or OUTBOUND_CALL_. This function checks if there are any active BTEs according to customizing.

 

articulobte1.JPG

 

 

According to its Interface there are 2 types of BTEs:

 

Publish & Subscribe interfaces. Can not update data. Possible to have multiple implementations

Process interfaces. Can update date. Only one active implementation

 

How to find a BTE (2 ways)

  • Search the source code for for "OPEN_FI_PERFORM" og " OUTBOUND_CALL_"
  • Use transaction FIBF menu Environment->Info System (P/S ). Use the Documentation button to see the documentation for the BTE.

ArticuloBte2.JPG

Implementing the BTE

 

  • Goto transaction FIBF menu Environment->Info System (P/S )

 

  • Select the BTE you want to implement. (as below)

  • Double click on Sample function module

 

  • Then we copy the sample function module to a Z-function module (First create a new function group for the function module) .

        Note: The name of the Z-function module is not important

 

  • Edit the code in the new function module:

 

  • Go back to transaction FIBF - Menu Settings->Products -> Of a customer and create a new product which identifies the new product .

        Remember to mark the Active field.

  • We enter the product name description and RFC destination if applicable. The check box A is to activate the product.

 

  • The next step is to link the function module and the event using the product created. Go back to FIBF menu Settings->P/S function modules->of a customer - Create an entry that links the BTE and Product with the new function module (as below)

articulobte1.JPG

 

  • And that´s all, we only need to save and activate.

JSON Adapter for ABAP Function Modules

$
0
0

JSON Adapter for ABAP Function Modules

This blog explains how to use an ABAP class that allows calling function modules with a URL and a JSON representation of ABAP data.

 

The code to the class presented in this blog is already available at SAP Code Exchange here:
https://cw.sdn.sap.com/cw/groups/json-adapter-for-abap-function-modules.

 

 

Introduction

ABAP Function Modules are a great thing. They provide encapsulation of functionality in an easy to use package. They are the basis of ABAP ability to interoperate with the external world, thanks to, mostly, two built-in features: the RFC library (where the F comes from "Function") and the SOAP adaptor, that allows SOAP compliant Web Services to be built from function modules.

ABAP Function Modules are also great for one more thing: they provide a name based parameter interface. This is something not new to ABAP developers, but this allows an incredible flexible way of parameter passing, including references, complex nested structures and tables. Shortly, with ABAP Function Modules you have the full power of the business suite at your service.

But, the RFC, while great, is a binary only library that could be cumbersome to use, and it is not available for all platforms. The SOAP adapter, while good for SOA enabled sites, could be also more complex that necessary and may have an overhead that makes it a non-option for some use cases, like, for example, data ready for consumption from user interfaces.


Just imagine how great could be to call a function module with a URL:

http://my.abap.server:8000/fmcall/rfc_system_info?format=json

 

and get the results of the function module call in JSON format:

 

{

     CURRENT_RESOURCES: 4,

     MAXIMAL_RESOURCES: 5,

     RECOMMENDED_DELAY: 0,

     RFCSI_EXPORT: 
{

          RFCPROTO: "011",

          RFCCHARTYP: "4103",

          RFCINTTYP: "LIT",

          RFCFLOTYP: "IE3",

          RFCDEST: "seppuku_NXK_00",

          RFCHOST: "seppuku",

          RFCSYSID: "NXK",

          RFCDATABS: "NXK",

          RFCDBHOST: "seppuku",

          RFCDBSYS: "ADABAS D",

          RFCSAPRL: "731",

          RFCMACH: " 390",

          RFCOPSYS: "Linux",

          RFCTZONE: " 3600",

          RFCDAYST: "",

          RFCIPADDR: "172.16.122.128",

          RFCKERNRL: "721",

          RFCHOST2: "seppuku",

          RFCSI_RESV: "",

          RFCIPV6ADDR: "172.16.122.128"

     }

}


Furthermore, imagine that the function module has input parameters, and we can pass them as part of the query string:

http://my.abap.server:8000/fmcall/bapi_flight_getdetail?airlineid=LH&connectionid=2402&flightdate=20130128&format=json

 

And get the details of the flight:


{

     ADDITIONAL_INFO: 
{

          FLIGHTTIME: 65,

          DISTANCE: "555.0000",

          UNIT: "KM",

          UNIT_ISO: "KMT",

          PLANETYPE: "DC-10-10",

          FLIGHTTYPE: ""

     },

     AVAILIBILITY: 
{

          ECONOMAX: 380,

          ECONOFREE: 0,

          BUSINMAX: 41,

          BUSINFREE: 0,

          FIRSTMAX: 18,

          FIRSTFREE: 0

     },

     FLIGHT_DATA: 
{

          AIRLINEID: "LH",

          AIRLINE: "Lufthansa",

          CONNECTID: "2402",

          FLIGHTDATE: "2013-01-28",

          AIRPORTFR: "FRA",

          CITYFROM: "FRANKFURT",

          AIRPORTTO: "SXF",

          CITYTO: "BERLIN",

          DEPTIME: "10:30:00",

          ARRTIME: "11:35:00",

          ARRDATE: "2013-01-28",

          PRICE: "242.0000",

          CURR: "EUR",

          CURR_ISO: "EUR"

     },

     EXTENSION_IN: [ ],

     EXTENSION_OUT: [ ],

     RETURN: 
[

          {

               TYPE: "S",

               ID: "BC_IBF",

               NUMBER: "000",

               MESSAGE: "Method was executed successfully",

               LOG_NO: "",

               LOG_MSG_NO: "000000",

               MESSAGE_V1: "",

               MESSAGE_V2: "",

               MESSAGE_V3: "",

               MESSAGE_V4: "",

               PARAMETER: "",

               ROW: 0,

               FIELD: "",

               SYSTEM: "NXK001"

          }

     ]

}

 

Isn't it great?

And then, imagine that you have to pass some ABAP structure or table to the function module. You could just go and write it in JSON and pass it to the function module in the payload of a POST request:

image001.png

 

This will be the response, showing that a new booking has been created:

 

image003.png


Have you noticed that in this case the name of the ABAP variables are in lower case? Have you also noticed the sequence "/lc" added to the end of the URL in figure 1?

ABAP internal variable names are always maintained in upper case. That's why this module defaults to upper case for the JSON string. But this is sometimes not pretty, and you may want to have the results to have var names in lower case. For this, just add a "lc" to the path info after the function module name.

 

 

Create the ICF Service

All this has been implemented in the ZCL_JSON_HANDLER class that is available in the JSON Adaptor for ABAP Funcion Modules code exchange project for you to download and use it.

Once you have the class installed, you only have to create an ICF handler in transaction SICF:

 

image005.png

Adjust logon data at will and then point your browser to the service URL. You are now ready to start!!

 

 

Output formats

JSON (the JavaScript Object Notation) is the basis for this class. The default output format is JSON and the only supported input format (apart form the query string params) is JSON. But, other possible output formats have been implemented.

JSON has been seeing an enormous increasing in its use in the last years, especially for its high suitability at being used directly in JavaScript based user interface communication, like HTML5 and AJAX. JSON can be consumed directly in a JavaScript program just by "evaling" it, and its low overhead has also find its way into many other languages and communication requirements.

JSON is normally invoked with the JavaScript XMLHttpRequest call, normally as an asynchronous call. But sometimes, the HTML tag <script> is used to place a JSON call. In this case, the <script> tag would need a real JavaScript program instead of a JSON string. For this, many servers implement a JSON "padded" call (or JSONP), in which the JSON data is sent as the parameter of a JavaScript function. This is normally indicated by the existence of a URL parameter with the name "callback", as seen here:


http://my.abap.server:8000/fmcall/rfc_system_info/lc?callback=jsfunction

 

Wich gives the following output:

 

jsfunction({"current_resources":4,"maximal_resources":5,"recommended_delay":0,"rfcsi_export":{"rfcproto":"011","rfcchartyp":"4103","rfcinttyp":"LIT","rfcflotyp":"IE3","rfcdest":"seppuku_NXK_00","rfchost":"seppuku","rfcsysid":"NXK","rfcdatabs":"NXK","rfcdbhost":"seppuku","rfcdbsys":"ADABAS D","rfcsaprl":"731","rfcmach":"  390","rfcopsys":"Linux","rfctzone":"  3600","rfcdayst":"","rfcipaddr":"172.16.122.128","rfckernrl":"721","rfchost2":"seppuku","rfcsi_resv":"","rfcipv6addr":"172.16.122.128"}});

 

This allows full operation for function modules calls from every kind of JavaScript application.

 

Once the basic JSON ABAP serialization was completed, I thought it would be a good idea to give you some choice in the output format, just as Google does with most of their public APIs.

 

Try this:

http://my.abap.server:8000/fmcall/rfc_system_info?format=xml

And you will get the output of the function call in a simple XML representation:


<RFC_SYSTEM_INFO>

  <CURRENT_RESOURCES>4</CURRENT_RESOURCES>

  <MAXIMAL_RESOURCES>5</MAXIMAL_RESOURCES>

  <RECOMMENDED_DELAY>0</RECOMMENDED_DELAY>

  <RFCSI_EXPORT>

    <RFCPROTO>011</RFCPROTO>

    <RFCCHARTYP>4103</RFCCHARTYP>

    <RFCINTTYP>LIT</RFCINTTYP>

    <RFCFLOTYP>IE3</RFCFLOTYP>   

    <RFCDEST>seppuku_NXK_00</RFCDEST>

    <RFCHOST>seppuku</RFCHOST>

    <RFCSYSID>NXK</RFCSYSID>

    <RFCDATABS>NXK</RFCDATABS>

    <RFCDBHOST>seppuku</RFCDBHOST>

    <RFCDBSYS>ADABAS D</RFCDBSYS>

    <RFCSAPRL>731</RFCSAPRL>

    <RFCMACH>390</RFCMACH>

    <RFCOPSYS>Linux</RFCOPSYS>

    <RFCTZONE>3600</RFCTZONE>

    <RFCDAYST/>

    <RFCIPADDR>172.16.122.128</RFCIPADDR>

    <RFCKERNRL>721</RFCKERNRL>

    <RFCHOST2>seppuku</RFCHOST2>

    <RFCSI_RESV/>

    <RFCIPV6ADDR>172.16.122.128</RFCIPV6ADDR>

  </RFCSI_EXPORT>

</RFC_SYSTEM_INFO>


And  this:

http://my.abap.server:8000/fmcall/rfc_system_info?format=yaml

In order to have the function module output in YAML format:

 

--- #YAML:1.0

CURRENT_RESOURCES: 4

MAXIMAL_RESOURCES: 5

RECOMMENDED_DELAY: 0

RFCSI_EXPORT:

  RFCPROTO: "011"

  RFCCHARTYP: "4103"

  RFCINTTYP: "LIT"

  RFCFLOTYP: "IE3"

  RFCDEST: "seppuku_NXK_00"

  RFCHOST: "seppuku"

  RFCSYSID: "NXK"

  RFCDATABS: "NXK"

  RFCDBHOST: "seppuku"

  RFCDBSYS: "ADABAS D"

  RFCSAPRL: "731"

  RFCMACH: "  390"

  RFCOPSYS: "Linux"

  RFCTZONE: "  3600"

  RFCDAYST: ""

  RFCIPADDR: "172.16.122.128"

  RFCKERNRL: "721"

  RFCHOST2: "seppuku"

  RFCSI_RESV: ""

  RFCIPV6ADDR: "172.16.122.128"


And  while I was at it, and remembering my Perl background, I thought it would be really fun to have ABAP just dump its data in Perl Data::Dumper format, which is quite close to JSON:

http://my.abap.server:8000/fmcall/rfc_system_info?format=perl

 

Have fun with Perl:

 

$RFC_SYSTEM_INFO = {'CURRENT_RESOURCES' => 4,'MAXIMAL_RESOURCES' => 5,'RECOMMENDED_DELAY' => 0,'RFCSI_EXPORT' => {'RFCPROTO' => '011','RFCCHARTYP' => '4103','RFCINTTYP' => 'LIT','RFCFLOTYP' => 'IE3','RFCDEST' => 'seppuku_NXK_00','RFCHOST' => 'seppuku','RFCSYSID' => 'NXK','RFCDATABS' => 'NXK','RFCDBHOST' => 'seppuku','RFCDBSYS' => 'ADABAS D','RFCSAPRL' => '731','RFCMACH' => '  390','RFCOPSYS' => 'Linux','RFCTZONE' => '  3600','RFCDAYST' => '','RFCIPADDR' => '172.16.122.128','RFCKERNRL' => '721','RFCHOST2' => 'seppuku','RFCSI_RESV' => '','RFCIPV6ADDR' => '172.16.122.128'}};

 

It shouldn't be difficult to create your favourite data representation, look at the code and develop your own ideas.

 

 

Available options

You can add several options as part of the URL path info and also as query string. This is a summary of them:

Path info options:

/lc -> outputs var names in lower case.

/yaml -> returns output in YAML format

/xml -> returns output in XML format

/perl -> returns output in Perl Data::Dumper format

(Path info options are not recommended if you want to keep a RESTful definition of a service).

Query string options:

?lowercase=X -> outputs var names in lower case (no available when using the built-in transformation ID)

?show_import_params=X -> include import params in the response

?callback=<callback_name> -> returns json output in enclosed in a JavaScript function call with name <callback_name>

?format=<data_format> -> returns output in specified <data_format>, which can be: json, xml, yaml, perl. Defaults to json.

Format is determined with the info sent by the client in the Accept HTTP header. If specified as an option, the info in the Accept header will not be used.

In the future, it is planned to have another option to create an input template for creating an input JSON message to the function module.

 

 

Acknowledgements and a bit of history

This little project doesn't come out of nothing, and as everything that is done nowadays in technology comes from the fact that we can see further as we are standing on the shoulders of giants. There are many JSON for ABAP development projects out there, and JSON support is already built-in in the core of ABAP. This nice feature is already available in this module for the JSON and XML converters.

 

It is a great characteristic of function modules implementation in ABAP that they have a dynamic way of defining function parameters. Additionally, function modules are already so well suited to be used in the model of direct request->response that is so in vogue today with the Web. I got the idea for all this from a very old blog post from Piers Harding: You don't need to use soap to keep your rpc clean. This is a very great article from 2004, and actually we implemented Piers' idea around that time, using YAML as serialization format. In 2006 we changed the main focus to JSON instead of YAML and we've been using that old version for a long time. Last year, some developments happening around made me have a look at all this, and could find the time to completely rewrite it cleanly and make it ready for publishing. So sorry for the delay, but it is better late than never. Hope this can still be useful to someone.

 

I could have never done this without the help of Juan Diez, who opened my eyes to the incredible richness of ABAP dynamic data programming. He actually developed parts of this and is the main user of this code. Thanks a lot Juan.


I also want to thank all my colleagues at SAP that have patiently listen to me (or at least pretended ) when I was talking about this and when I showed them the examples. I also thank them for their patience at standing at my ever changing mood: Ernesto, Félix, Miguel Angel, José, Cony, Cristina and Aviad, thanks a lot for being there.

 

 

Getting the code

The code is ready to be downloaded and used under the terms of use of SAP Code Exchange. You can get it here: https://cw.sdn.sap.com/cw/groups/json-adapter-for-abap-function-modules.

Join the project and please report bugs and ideas for improvement. Thank you!.

 

Connectivity and Integration in ABAP – Part 1

$
0
0

SAP Netweaver 7.4 is now available for customers and it comes with exciting new capabilities for connectivity and integration in ABAP. In this series of blogs, I will introduce you to the newest of the new but I will also explain the implications for customers on SAP NetWeaver 7.02 and higher. I will try not to forget those of you who are unlikely to be on SAP Netweaver 7.4 for some time, as there is plenty of scope for you to make better use of the existing capabilities and, in the process, be in a position to more easily take advantage of the latest features if and when you finally arrive at SAP Netweaver 7.4.

 

ABAP 7.40 lets you reduce TCO/TCD of application scenarios that run across multiple ABAP systems. That is a bold statement but I believe it to be true. It is the result of several years of development addressing the needs of both administrators and developers. But what sort of connectivity am I talking about here? Point-to-point. Surely that is a recipe for trouble in large landscape and complex scenarios? Not now it isn't, if you do it right. Specifically what sort of point-to-point connectivity is it? That is a little bit more complex and will be the subject of a more detailed blog. For the moment let me say that this latest development comes out of the group responsible for ABAP Web services so that is the first connectivity type that I will cover.

 

It was an ambitious project and addressed a diverse range of issues. Here is a selection of key capabilities:

 

  • Automated setup of complete applications across multiple systems
  • Enable the addressing of business entities out of connectivity networks
  • Easy development and connectivity-type-agnostic application programming in ABAP

 

Some of the benefits for customers are:

 

  • Greater operational continuity enabled by central management, visibility, and control of the services landscape via a single toolset
  • Reduce the costs and risks associated with complex business processes involving multiple services and systems
  • Robust application scenarios through decoupling of business and technical configuration

 

I will address these and more in the coming blogs.

 

To finish, here is the heads up for TechEd 2013  - I will be covering aspects of this in a workshop entitled Automated Connectivity between Business Applications. I look forward to seeing you there.

Usage of the ABAP REST Library [SAP_BASIS 7.40]

$
0
0

Overview

 

Objective: Provides a brief introduction to REST services in general and explains the basic usage of the ABAP REST library (SAP_BASIS 7.40) in particular.

Applies to: SAP NetWeaver AS ABAP since 7.00. The SAP documentation for REST is available in AS ABAP 7.40.

Target Group: Develpers, architects, consultants, project leads and decision makers who want to gain first information on the ABAP REST library.

 

 

 

 

Introduction

 

This blog post gives a short introduction to the new ABAP library for REST based services, which is part of ABAP 7.4. In particular in combination with the new JSON support in ABAP 7.4, it can be used writing native HTTP services directly on top of the Internet Communication Framework (ICF) following a RESTful design.

 

The REST library is used as a central component in the following frameworks:

  • SAP NetWeaver Gateway as a core of the OData support (OData is the REST-based protocol recommended by SAP for business scenarios).
  • ABAP Development Tools for SAP NetWeaver (also known as ABAP in Eclipse) as part of its connectivity infrastructure, which is treating ABAP development objects as resources.

It should be stressed that the REST library is a technical library and does not provide a programming, configuration or operations model on top of the ICF.

For productive business scenarios SAP recommends to use the SAP NetWeaver Gateway which provides all qualities for REST services based on the OData standard.

 

Note that the core SAP NetWeaver Gateway infrastructure is also directly part of the SAP NetWeaver Application Server ABAP since release 7.4.

 

What is REST?

 

REST is an acronym for Representational State Transfer and stands for a programming paradigm for Web applications. The REST architectural style was first introduced by Roy Fielding (Link).

 

The central principle of REST services is the existence of resources each of them referenced with a unique resource identifier (URI). The reading and manipulation of these resources is based on a standardized interface which is the HTTP protocol. Via the HTTP protocol, client and servers exchange and transfer representations of resources, i.e. documents conveying the information of a resource.

 

When to use it?

 

There is a lot of information available in the Web about that question (and the alternatives to REST, e.g. SOAP based Web Services), so only some main indicators should be mentioned here:

  • you have a stateless service,
  • the support of (web) caching infrastructures is possible,
  • the commmunication design is based on a point-to-point integration with no formal description of an interface, i.e. clients and servers agree out of band on the data format exchanged (e.g. in SOAP based Web Services, the data exchange format is pre-defined),
  • frontend technologies may be of specific interest, i.e. REST services may be easier integrated and consumed by frontend platforms using their own technical basis (e.g. plain XML).

 

The ABAP REST Library

 

The ABAP REST library specific classes are located in package SREST and its subpackages (SREST_CORE, SREST_EXT, SREST_HTTP, SREST_TEST, SREST_UTIL). If you want to use the ABAP REST library, you have to declare an access usage for the corresponding package interface. Using the ABAP REST library, the focus in this blog is on developing REST services in the context of the HTTP protocol, i.e. the URIschema is http or https, the authority identifies an ABAP application server, the SAP web dispatcher or some network intermediate (e.g. reverse proxy, which is set up to forward a request to an ABAP application server). The path consists of the base path (defined in the ABAP Internet Communication Framework (ICF) for a REST service's handler class) and the resource path, used by a REST application service to identify the requested resource.

 

ABAP REST Service Processing Overview

 

This picture gives a rough overview on the control flow when processing a REST request within an ABAP application server.

 

startup2.png

These are the 3 main processing steps (see picture above):

  1. Any incoming HTTP request on an ABAP application server is processed by the Internet Communication framework (ICF). The ICF contains a service registration where all ICF related services and their handler classes are registered (done via transaction SICF). The handler class is an ABAP class which implements the interface method IF_HTTP_EXTENSION~HANDLE_REQUEST.
    This is also the case for ABAP REST services, where the ABAP handler class is derived from class CL_REST_HTTP_HANDLER.
  2. Within the HTTP handler method HANDLE_REQUEST:
    • The HTTP request object and response object get wrapped by corresponding REST request and response objects and are available to the REST service processing later on,
    • a REST context is created with general information (URI parts, ICF service name, etc.) to be available in the REST service processing,
    • the REST application is called containing its list of addressable resources with their identifiers and processing class(es),
    • the REST router finds the processing class for the used URL,
    • the application's method to access the resource is called based on the used HTTP verb (GET, POST, PUT, DELETE, HEAD, OPTIONS).
  3. Inside the identified resource class and method, the data processing takes place.

 

REST Service Implementation

Used interfaces and classes of an ABAP REST service implementation:

  • CL_REST_HTTP_HANDLER
    This is the base handler class for ABAP REST applications, i.e. each REST application has to derive its handler class from this class and provide implementations for specific interface methods which get called during request processing.
    This class comprises the interfaces IF_HTTP_EXTENSION and IF_REST_APPLICATION used by the REST framework to handle requests. The REST application's derived handler class is the class entered in the SICF transaction as the HTTP handler. You can look at this class as the clue between the HTTP and the REST environment.
  • IF_HTTP_EXTENSION
    This interface is part of the ICF and is used to register a handler class in the ICF. It is part of the REST handler class CL_REST_HTTP_HANDLER.
    Whenever a HTTP request hits the ABAP application server, the path gets resolved by the ICF, the registered class gets instantiated and its method IF_HTTP_REQUEST~HANDLER_REQUEST is called. The class CL_REST_HTTP_HANDLER already contains the implementation for this method.
  • IF_REST_APPLICATION
    Declares the method GET_ROOT_HANDLER, which must be implemented by any ABAP REST application to return an instance for interface IF_REST_HANDLER (see below) for handling specific REST requests.
  • IF_REST_HANDLER
    This interface is used to call classes designed to process REST requests. For this, it declares the method HANDLE, which gets passed in the following objects from the REST framework: Request, response and context. You do not implement this interface seperatly for your REST service, but you use it via an class derived from CL_REST_RESOURCE (see below).
  • IF_REST_RESOURCE
    This interface defines the methods GET, POST, PUT, DELETE, HEAD and OPTIONS reflecting the HTTP methods for the REST resource. It is not required to implement this interface solely by the REST application developer, but inherit it from CL_REST_RESOURCE instead.
  • CL_REST_RESOURCE
    This class is the designated base class for all REST resources. Any REST application resource class inherits from this class and overwrites methods to implement application resource specific behavior. For each HTTP verb this base class contains a default implementation (returning HTTP 405: Not Allowed) so each method you want to support has to be overridden by your REST resource class.
    This class implements the interfaces IF_REST_HANDLER and IF_REST_RESOURCE.
  • CL_REST_ROUTER
    This class gets used to resolve REST resource handler classes from the used URIs during the REST request processing. The router is normally instantiated in the IF_REST_APPLICATION~GET_ROOT_HANDLER method of the REST application class. This method returns the handler class for a REST request implementing interface IF_REST_RESOURCE.
    The selection of the handler class is based on a URI template associated with a handler class. That assignment of handler class to URI template is done by calling the ATTACH method of the rest router instance of type CL_REST_ROUTER.

 

All interfaces and classes, their usage and background information is available via SAP help (SAP Basis Release 7.40).

Especially the REST Programming Tutorial is of interest for people to have a first hand on ABAP REST programming.

 

This picture shows the sequence of class usage on the REST server side:class_flow.png

 

Example Service Implementation

 

The REST example used in this blog is using parts of the well-known SAP Flight sample data model. In this example, the resources are "flight" resources made available to REST clients stored in the database table SFLIGHT.

 

Defining the REST Application Class

 

The REST application class named ZCL_SCN_BLOG_FLIGHT_APP is dervied from the ABAP library class CL_REST_HTTP_HANDLER. This REST service class inherits especially the interface method IF_REST_APPLICATION~GET_ROOT_HANDLER. Within this method, at least one URI template gets attached to a REST handler class (multiple attachments are possible depending on the number of supported URIs and used classes). The handler class is named ZCL_SCN_BLOG_FLIGHT_RESOURCE in this example (for its implementation see below).

 

This is the implementation of method IF_REST_APPLICATION~GET_ROOT_HANDLER in the REST application class ZCL_REST_FLIGHT_APPL:

 

method if_rest_application~get_root_handler. 

  data(lo_router) = new cl_rest_router( ).
  lo_router->attach( iv_template = '/flights' iv_handler_class = 'ZCL_SCN_BLOG_FLIGHT_RESOURCE' ).
  lo_router->attach( iv_template = '/flights.{format}' iv_handler_class = 'ZCL_SCN_BLOG_FLIGHT_RESOURCE' ).
 

  lo_router->attach( iv_template = '/flights/{carrid}' iv_handler_class = 'ZCL_SCN_BLOG_FLIGHT_RESOURCE' ).
  lo_router->attach( iv_template = '/flights/{carrid}.{format}' iv_handler_class = 'ZCL_SCN_BLOG_FLIGHT_RESOURCE' ).

  lo_router->attach( iv_template = '/flights/{carrid}/{connid}' iv_handler_class = 'ZCL_SCN_BLOG_FLIGHT_RESOURCE' ).
  lo_router->attach( iv_template = '/flights/{carrid}/{connid}.{format}' iv_handler_class = 'ZCL_SCN_BLOG_FLIGHT_RESOURCE' ).

  lo_router->attach( iv_template = '/flights/{carrid}/{connid}/{fldate}' iv_handler_class = 'ZCL_SCN_BLOG_FLIGHT_RESOURCE' ).
  lo_router->attach( iv_template = '/flights/{carrid}/{connid}/{fldate}.{format}' iv_handler_class = 'ZCL_SCN_BLOG_FLIGHT_RESOURCE' ).

  ro_root_handler = lo_router.

endmethod.

 

In this code example, multiple URL templates are attached to one and the same resource class ZCL_SCN_BLOG_FLIGHT_RESOURCE (which is not mandatory). For the details on the definition on URI templates, you may refer to the SAP documentation. In this blog only an example is given for the routing.

 

Example (Routing):

Assume that the URL 'http://ldai3yi3.wdf.sap.corp:50034/sap/bc/rest/scn_test/flights/LH/0400.json?sap-client=000' is called with the HTTP GET method.For the correct routing of the request to the method IF_REST_RESOURCE~GET in class ZCL_SCN_BLOG_FLIGHT_RESOURCE, the class ZCL_SCN_BLOG_FLIGHT_APP has to be entered in the ICF as handler class (e.g. in the ICF node 'SCN_SAMPLE'). The URI-attributes 'carrid' is set to value 'LH', the attribute 'connid' is set to value '400' and the 'format' attribute is set to 'json'. All these attributes get set by the REST framework and are available in the request object in the resource processor class ZCL_SCN_BLOG_FLIGHT_RESOURCE (defined as data members in its base class CL_REST_RESOURCE).

 

 

Defining the REST Resource Class

 

In this example, the class representing the "flight" resource is named ZCL_SCN_BLOG_FLIGHT_RESOURCE and is derived from the REST library class CL_REST_RESOURCE. It implements the methods of interface IF_REST_RESOURCE. The interface methods GET and OPTIONS are supported, i.e. this method is implemented in the resource class (note: methods not implemented will hit the default implementation of CL_REST_RESOURCE and return a HTTP 405 return code: "HTTP method '...' not supported").

 

This is the implementation of method IF_REST_RESOURCE~GET in class ZCL_SCN_BLOG_FLIGHT_RESOURCE:

 

method if_rest_resource~get.

 

  data: lt_flight type table of sflight.

 

  data(lo_entity) = mo_response->create_entity( ).
  data(lv_carrid) = mo_request->get_uri_attribute( iv_name = 'carrid' ).
  data(lv_connid) = mo_request->get_uri_attribute( iv_name = 'connid' ).
  data(lv_fldate) = mo_request->get_uri_attribute( iv_name = 'fldate' ).

 

  if ( lv_fldate is not initial ).
    select * from sflight into table lt_flight

      where carrid = lv_carrid and connid = lv_connid and fldate = lv_fldate.
  elseif ( lv_connid is not initial ).
    select * from sflight into table lt_flight

      where carrid = lv_carrid and connid = lv_connid.
  elseif ( lv_carrid is not initial ).
    select * from sflight into table lt_flight

      where carrid = lv_carrid.
  else.
    select * from sflight into table lt_flight.
  endif.
 

 

  data(lv_format) = mo_request->get_uri_attribute( iv_name = 'format' ).


  if ( lv_format = 'json' or lv_format is initial ).
    " Transform data to JSON
    data(lo_json_writer) = cl_sxml_string_writer=>create( type = if_sxml=>co_xt_json ).
    call transformation id source itab = lt_flight result xml lo_json_writer.
    lo_entity->set_content_type( if_rest_media_type=>gc_appl_json ).
    lo_entity->set_binary_data( lo_json_writer->get_output( ) ).
  elseif ( lv_format = 'xml' ).
    " Transform data to XML
    call transformation id source itab = lt_flight result xml data(lv_xml).
    lo_entity->set_content_type( if_rest_media_type=>gc_appl_xml ).
    lo_entity->set_binary_data( lv_xml ).
  elseif ( lv_format = 'atom' ).
    " Transform data to Atom
    data: ls_feed   type if_atom_types=>feed_s,
          ls_entry  type if_atom_types=>entry_s.
    ls_feed-id-uri = 'http://www.sap.com'.
    get time stamp field ls_feed-updated-datetime.
    loop at lt_flight assigning field-symbol(<f>).
      ls_entry-title-text = | { <f>-carrid }-{ <f>-connid }|.
      convert date <f>-fldate

        into time stamp ls_entry-updated-datetime time zone 'UTC'.
      ls_entry-title-type = if_atom_types=>gc_content_text.
      append ls_entry to ls_feed-entries.
    endloop.
    data(lo_provider) = new cl_atom_feed_prov( ).
    lo_provider->set_feed( ls_feed ).
    lo_provider->write_to( lo_entity ).
  endif.

  mo_response->set_status( cl_rest_status_code=>gc_success_ok ).

endmethod.

 

Brief description of the implementation:

  • A REST response entity gets created for the service's response. This entity will carry the flight data.
  • All (potentential) URI attributes are read (the names are defined in the routing class above) and set in local variables.
  • Depending on the specified resource URI attributes, the data is retrieved from the data source (the database table SFLIGHT).
  • The format setting attribute is used to identify the requested response format. The implementation offers three formats:
    • json (default): The SXML library (with stream type json) gets used together with the ID transformation to set the content formatted in JSON.
    • xml: The ID transformation is used to transfer the data into XML format.
    • atom: An ATOM feed is created to return the data to the REST client.

 

This is the implementation of method IF_REST_RESOURCE~OPTIONS in class ZCL_SCN_BLOG_FLIGHT_RESOURCE:

 

method if_rest_resource~options.

  mo_response->set_header_field( iv_name = if_http_header_fields=>allow iv_value = 'OPTIONS,GET' ).

  mo_response->set_status( cl_rest_status_code=>gc_success_no_content ).

endmethod.

 

Brief description of the implementation:

  • The OPTIONS method is generally used by a client to determine the options available with an associated resource or the capabilities of a service without calling any action or getting any resource.
  • The method returns the supported HTTP verbs in HTTP header 'Allow', which is for this REST service GET and OPTIONS.
  • The header and the status is set in the (wrapped) REST response object of the resource class.

 

 

Additional Supported Features

 

Beside the basic functionality of a REST service mentioned above, there are additional features integrated into the ABAP REST library (which are only mentioned in this blog without detailed explanation (for more in depth information refer to the SAP documentation):

  • Content Negotiation
    This is a mechanism defined in the HTTP specification that makes it possible to serve different versions of a document or different resource representations could be available with the same URI. Content negotiation is the process of selecting the best representation between client and server for a given resource when multiple representations are available.
  • Conditional Handling
    This is an optimistic concurrency control mechanism for accessing identical data resources used by different clients. The implementation of this control is done in the environment of REST data service using by the concept of ETags (entity tags) which were introduced by the HTTP/1.1 protocol.
  • Multipart Requests
    Beside single content per HTTP request submitted by a REST client, it is possible to pack multiple requests into one request entity, which becomes a multipart entity. When doing so a REST client saves the overhead of sending a sequence of single requests to the server (e.g. reduces network latency etc.).

Connectivity and Integration in ABAP – Part 2

$
0
0

For my second blog in this series, I want to focus on one of the key connectivity capabilities of SAP NetWeaver 7.4 – that is, the automated setup and configuration of complete applications across multiple ABAP systems.

 

Imagine you are someone with an interest in a business, but not in the technical details of the underlying system landscape. You know your business entities – for example, sales offices, regional offices, warehouses, and so on – and what they are called and what they do in the real world.  You also have a business scenario that somehow requires the various business entities to communicate with each other. Now imagine that you are part of a large organization with several hundred systems and thousands of administrators and/or persons responsible for individual systems.

 

Maybe this business scenario of yours runs over several ABAP systems/clients and is implemented entirely with asynchronous Web services. That would mean configuration has to be done in all systems and that means making changes to the productive systems (What! - Did you not know that you cannot transport configurations?).

 

The Manual Way is Hard

 

Now you have a problem or two. Who do you talk to? Does anyone understand you – you speak “business”, they speak “technical”. What systems are affected? How do you fill out system-specific spreadsheets so that changes will be allowed and then correctly implemented? Somehow you have managed to identify and persuade all the right people to make the necessary changes. How do you know that each manual step has been done correctly? The process depends on so many people. These people are not necessarily specialists for the changes that have to be made and have, almost certainly, no overall perspective about the end result.

 

Central Management makes it Easy

 

Now it is almost magical. What you need to know is vastly reduced. You need to know that an integration scenario definition exists that matches your needs and you need the names of the business entities that are to be connected. You need to find the relevant person responsible for handling the scenarios in the central system. The information you provide is understandable to you, and it is understandable to the administrator because all the necessary information is available in the central system through SOA Manager (transaction SOAMANAGER). When the scenario is configured and the changes released to the landscape, the status of the changes is available in the central system.

 

It is almost magical, but not quite. The integration scenario definition that is found in SOA Manager is something that has to be developed. There is a dedicated perspective in the Eclipse-based ABAP Development Tools for integration scenario definitions and related objects (it is also possible in SE80). That is a topic for a future blog.

Connectivity and Integration in ABAP – Part 3

$
0
0

In my previous blog, I highlighted advantages of automating the configuration of distributed applications running over multiple systems. A prerequisite for automated configuration is a central SOA Manager system with a landscape of managed systems. Such a landscape is advantageous even if you are not yet ready to make the leap to scenario-based configuration. I want to encourage you to take the first small steps in this direction, so here I will explain what you need to do to set up the systems to be central or managed respectively.

 

The setup is simple using SOA Manager (I am assuming here that your systems are already fit for Web services). You make one client a central system, and in each of the other systems maintain a management connection to the central system.

 

Picture1.png

 

First you need to define one SOA Manager as the central SOA Manager. You go to the relevant client and start transaction SOAMANAGER. Somewhere under Management Connections you will find a check box called Central System. Select this and choose Save. Enter the user and password for Meta Data Access (so the central system can manage itself) and click OK. Refresh the browser (with F5) to see the changes.

 

central_configuration.gif

 

In each of the other systems, you can now maintain a new management connection to the central system. Because the management connection is a two-way connection, you have to specify the connection and authentication information for both ends of the connection.

 

management_connection.gif

 

Once you have done that, click the Start Setup button and everything will be done automatically. You can then check the status on the Existing Connections tab.

 

That is all you need to do.

 

Now, for example, you can run health checks from the central system. You can gather log data from the managed systems. Also the registries are automatically set up so that a service configured in a managed system will be published to the central registry, which simplifies single Web service configuration.


Connectivity and Integration in ABAP – Part 4

$
0
0

Sometimes it is the little things that make the difference. Although I would normally recommend using central configuration, most of you are still going into individual systems to do your configuration. So I want you to know that this is also easier between two managed systems (see my previous blog for how to set up a landscape of managed systems connected to a central system).

 

Each ABAP system has a local Web services registry to which services are published when they are configured. There is much you can do to determine what and where things are published, but I am ignoring those options and will concentrate on the default behavior. When you set up the management connection to a central system, the system is automatically configured to publish to the Web services registry on the central system – I will call this the central registry.

 

How does this help? Say you want to configure a consumer in a system in a managed landscape. Because you are in a managed landscape, you know that the service you are interested in will have been published to the central registry. That alone is a big help. However, a very nice feature of the ABAP-based Web services registry is the option to search for compatible bindings.

 

search_compatible_bindings.gif

 

If you choose the checkbox “Search for Compatible Bindings”, only those will be displayed. You do not even need to enter the name of the service that you are looking for. If there are several bindings that are compatible with the consumer, these will be displayed with an index that is an indication of the level of recommendation. For clarity, only the property values that are different are displayed, though you can pull up more information if you wish.

 

That is just one way that an ABAP-based services registry can make your Web-service life easier.

Connectivity and Integration in ABAP – Part 5

$
0
0

When the ideal world meets the real world, the real world wins.

 

When central configuration was first introduced in SAP NetWeaver 7.02, I had an idealized view of how the change processor should work. Click one button and the entire landscape would be checked and updated where necessary. You would always be certain the landscape was in good order after the change processor had run. If you had a problem somewhere in the landscape, click that button and everything would be fine.

 

In the real world, there are often landscapes of several hundred systems (not the half dozen that I get to play in) and administrators wanted the ability to select the systems that should be involved. This could be particularly relevant in the larger landscapes– if you know that there are only changes to be made to a specific system, you can spare the central and managed systems unnecessary work. For example, you may have skipped a processing phase in one system during an earlier run of the change processer (the system may have been down or some other problem encountered) and you only need to update the configurations there.

 

change_processor.gif

 

The option to release individual systems or selected systems (see screenshot) is already in SAP NetWeaver AS ABAP 7.4. If you want similar functionality in other releases, use SAP Note 1866268 - Create and Change Information in Change Processor.

Connectivity and Integration in ABAP – Part 6

$
0
0

Evolution and Co-existence of Programming Models

 

Several new concepts were introduced with SAP NetWeaver 7.4. There are significant advantages associated with the new way of doing things but the existing applications also continue to be supported. Let us consider the characteristics of the main options you have when programming with Web services in ABAP.

 

 

Single Service

 

Picture1.png

 

You can manually configure individual Web services. The application names the logical port to be used and decides in the application coding which interface is being used. Even if you adopt central configuration in a big way, you may still find use for this option occasionally.

 

Service Group and Mass Configuration

 

Picture2.png

 

Service groups were introduced for mass configuration of related Web services. The Web service infrastructure and configuration controls connection management to external parties. Central configuration of Web services based on service groups is possible with SAP NetWeaver 7.02.

 

 

Consumer Factory and Integration Scenario

 

Picture3.png

 

Central configuration of an entire integration scenario running over multiple systems is easy, and the addressing can be on a more fine-granular level than client. The application is only loosely coupled with its process integration – you may already be familiar with logical receiver determination but now there are further options relating to sender or authentication. Perhaps less obvious is the significance of the consumer factory in decoupling the application from the specific implementation – see diagram above. I will let you consider that for a while.

The Right Way to Create Web Services from Function Modules

$
0
0

The two main ways of developing Web services in the ABAP environment are often referred to as inside-out and outside-in. Inside-out refers to taking existing functions – for example, remote-enabled function modules and making Web services out of them. However, the recommended way of development is normally outside-in – that means modeling the service first and then implementing the methods.


The big advantage of the model-first approach is that you have complete control over the signature of the service. If you simply take an existing RFC function module and let the interface be generated, you have no influence over how it looks.


Since SAP NetWeaver 7.02 you can model in the ABAP environment and expose only what you want of your function module.


Metadata Repository (MDR)

 

The Metadata Repository is integrated in SE80 and is a local repository. This is in contrast to the Enterprise Services Repository (ESR), which is by its nature a central repository.


Some types of users who might be interested in MDR:

  • Customers who do not use SAP middleware, and have been creating Web services in ABAP using generation from remote-enabled function modules.
  • Customers who use SAP intensively, have a strong governance team, have SAP NW PI and the ESR but want to do a proof-of-concept development that requires minimal effort regarding governance, infrastructure, and so on.
  • Partners and consultants doing in-house development before moving objects to customer systems. For example, when the customer’s development system is not available for the start of the project.

 

Improvements in SAP NetWeaver 7.4


If you have been using the Metadata Repository in earlier releases, you may have noticed that displaying all the objects of large services can take a fairly long time. To make working with such services much smoother, only the higher level objects are now displayed by default and a sort of lazy loading has been introduced so that objects are loaded and displayed as required. Of course you can still choose to “Load All” (you can find this on the Objects tab) and wait a bit - then you have everything to hand as before.

 

Picture1.png


The search functionality for enterprise service objects in SE80 is also much improved. More detailed search criteria are possible and you can search for more object types. For example, you can now search for a service group without the need to know the relevant package. The backend Metadata Repository objects that are important for the new Integration and Connectivity in ABAP are also supported.

 

Picture2.png

Summary

 

For some scenarios it is over-the-top to install an ESR when all you need is some quick and simple modeling capability (perhaps a delivered service definition requires a minor modification). Or perhaps, you are just starting out in Web services and are not yet prepared to go the full way of SOA with all the governance and so on. Here the ability to create models directly in the ABAP system is useful.


Although SOAP-based Web services are no longer in fashion, support for them in the ABAP environment is getting better and better.

Who Has Read Your Sensitive Information?

$
0
0

You are concerned with security? You have done everything to make sure the connections to your system are as secure as possible? Connections will be made and these connections will be used by authorized users. Maybe some users abuse trust and look at data that they should not.

 

How is this relevant to Web services or RFC? The new Read Access Logging (RAL) capability logs access that has occurred through certain so-called channels. Many channels are used to access data but, important to note, the three channels that are currently supported are Web services, various flavors of RFC, and Web Dynpro. So, if it is important for you to know who has read your sensitive information, you need to take RAL into account when planning connections to that data.

 

I have presented a very narrow view of RAL but, if this blog wakes your interest, check out the documentation on Read Access Logging and then engage with the experts in the Security space.

Write for Web Services but use RFC

$
0
0

Consumer Mapping

 

If you have an application that is going to communicate with several systems (on different releases, for example), the services in the various systems may expect different communication types.

 

You can write an application to call a particular service but then, without changing the coding, call a different service that is semantically similar. I will illustrate this, with what is almost the „hello world” of consumer mapping, using a call to a Web service and then a call to an equivalent remote-enabled function module. An even simpler example would involve two Web services but the reasons for doing that may not be so obvious.

 

Starting Point

 

In a provider system I exposed a function module as a Web service. In the consumer system I created a corresponding consumer proxy. The application calls the Web service in the usual way by specifying the consumer and the logical port.

 

Picture1.png

 

Second Provider System

 

In another system there is no corresponding Web service but there is the same function  module as used in the Web service – you don’t get much more semantically similar than that and it makes the mapping trivial.

 

Picture2.png

 

A Little Development

 

I do not touch the application and the existing Web service consumer is not capable of calling the function module directly. So I first create an RFC consumer that will communicate with the function module. Then I create the consumer mapping between the Web service consumer and the RFC consumer. The Web service consumer is the “source consumer” and the RFC consumer is the “target” consumer”. Because there is only one target this is called single target consumer mapping (STCM).

 

Picture4.png

 

Configuration and Execution

 

The final step is to configure the STCM in SOA Manager. This is basically specifying the logical port, which in this case entails configuring an RFC destination. For the application, nothing has changed. But now, depending on the logical port used, the call is made to the naked function module.

 

Picture3.png

 

Conclusion

 

Since STCMs are a separate type of ABAP development object, a customer or partner, or other organization, could create their own STCM objects in order to manipulate outbound messages without modifying the sending application. For example, a web service consumer in an SAP-delivered scenario does not exactly match the service in an existing landscape. The customer could create the appropriate STCM based on the service consumer and the data structure of the addressed service, and do the mapping to provide the correct message structure.

Integration and Connectivity in ABAP – Part 7

$
0
0

One of the key concepts introduced in ABAP 7.40 involves the addressing of entities at a finer granular level than the client. These entities are called Identifiable Business Contexts (IBCs). Some of the advantages of IBCs are:

 

  • By addressing business entities instead of technical systems, your application scenarios will be more robust because of the decoupling of business and technical configuration. The scenarios will be independent of hardware, for example.
  • The size and complexity of messages will be reduced because some information for processing messages that was previously included in the payload is not needed.
  • Security can be enhanced because you can restrict access to dedicated business entities rather than giving general client level access.

 

If you want to know more, see the documentation Semantic Addressing Using Identifiable Business Contexts (IBCs) . To take away some of the mystery, I provide screenshots here illustrating where you can find IBCs and how to create your own.


Client IBCs

 

Each client has an IBC of its own that is automatically generated. So you can still do client-level addressing without creating other IBCs. Here is transaction SOAMANAGER:

 

client_ibc.png

 

In a managed landscape, the central system has an automatically generated IBC reference for each system client in the landscape, including itself.

 

client_ibc_references.png

 

Creating Your Own IBCs

 

Before you can create an IBC, there needs to be a class in the system that specifies the types and names that are allowed to be created. Here is transaction SE24:

 

se24_ibc1.png

 

The class must implement the interface IF_SRT_WSP_IBC_APPLICATION.

 

se24_ibc3.png

 

se24_ibc2.png

 

I only need to implement the LIST_RECEIVER_TYPE and LIST_RECEIVERS. I chose silly values for this demonstration so that they are easily identified and understood in the following screenshots.

 

se24_ibc5.png

 

se24_ibc4.png

 

Once the class is activated, it is visible in transaction SOAMANAGER. At this point there are no IBCs or IBC references based on the class.

 

soamanager_UI3_223_ibc1.png

 

What I am about to show is not recommended. In general applications should create IBCs, but it is easier to understand if I show the manual way.

 

In a managed system, I now create an IBC.

 

soamanager_UI3_223_ibc2.png

 

I choose Create and then, on the following screen, Save.

 

soamanager_UI3_223_ibc3.png

 

Now the IBC is visible when I search for it.

 

soamanager_UI3_223_ibc4.png

   

Now in the central system there is an IBC reference that has been automatically generated.

 

soamanager_UI3_222_ibcref1.png

   

Conclusion

 

There is nothing difficult about creating IBCs and IBC references, and they open up a whole load of new possibilities.


Convergence of Connectivity Technologies

$
0
0

SAP has strategic focus on HANA and Mobile and so most announcements and lectured last SAP TechEd covered these topics. But there are more exciting news and more TLAs (three letter acronyms to learn among them ACO, MDR and IBC. Never heard of these features? Then go ahead. Especially if you work in systems integration you should definitely read the following blog.

SOAP is not dead

 

In the current SPs of NW 7.02 SAP deployed the MDR (Metadata Repository) which is, despite of its stupid and generic name, an ABAP version of the ESR (Enterprise Services Repository). This makes it possible to model webs services in ABAP without using any Java components.

 

By the way, did you have a look at transaction SOAMANGER within the last time? It’s getting really cool: you can configure service groups in ABAP like you do in PI, there are UIs for mass configuration and even a health care check. Transaction SOAMANAGER is getting really useful!

 

All new features are described in OSS note 1575707 - ABAP Web Service Connectivity - New Features in Release 7.02.  In fact I’m loving this note because it contains a link to an SCN wiki page! In this note SAP is giving up the strategy of information silos and creates synergy which I really appreciate cause documentation which is for me an affair to the heart.

 

But there is even more: testing gets easies configuration-less shortcuts which is documented also in the wiki: http://wiki.sdn.sap.com/wiki/display/ABAPConn/Configuration-less+Shortcut+and+Generic+Consumer+Proxy.

ACO - RFC goes JCo/NCo

 

RFC has one major issue compared to web services: all RFC are exposed for remote access in contrast to web services that have to be configured with transaction SOAMANAGER. In NW 7.40 it will be able to switch RFCs on and off which is a quite useful feature.

 

But in NW 7.40 it’s getting real cool: the SAP ABAP Connector (ACO) allows you to generate so called consumer proxys which will leverage RFC scenarios which are a mess when dealing with different versions. If the signature of an RFC changes a call from ABAP with the old signature will not lead to a dump like an ususal function call but scramble your data.

Service Mappings in AS ABAP SOAP runtime

 

The same concept of parameter mapping that help you with dealing of new versions of an RFC interface are available in NW 7.40. Using so called contract, semantic contracts and “semantic addressing” using so called Identifiable Business Contexts (IBC).

 

Although those names (semantic contracts, IBCs) are terrible in my humble opinion -autistic savants might judge different about it- the concept are cool: You can define integration scenarios and cope with compatible and incompatible further development of service interfaces without using a PI.

Service Mappings in AS ABAP Designtime

 

In NetWeaver 7.31 SAP introduced a cool tool which I tested in a Customer Enganegemt Initiative some time ago. With the Service Implementation workbench you can develop full blown Enterprise Services resp. ESR content from BAPIs. You can use the tool as well to generate implementation classes from web services proxies that integrate all useful tools from SAP NetWeaver: Error and Conflict Handler, Idempotency Framework, Mappings and much more.

This leads in fact to another line of convergence: “good old” InsideOut development of web services from BAPIs which inherit their terrible properties is obsolete – RFCs can be exposed as Enterprise Services.

 

Does SAP NetWeaver PI become obsolete?

 

I don’t think so. If you want to design and administrate A2A and B2B process in an enterprise or cross-enterprise context AS ABAP is –at the moment- not the right choice but SAP Process Integration ist. I don’t want to speak of SAP Process Orchestration which has further unique properties and capabilities.

What comes next?

 

Because I took part at some HANA related rampups I was able to test SAP NW 7.40 very early. Now the documentation is online and it is GA I can start to blog about features – not only about HANA but also about connectivity especially about the so called concept of Unified Connectivity which comes with NetWeaver 7.40 and is already mentioned in SAP Library.

 

Up to now I like SAP’s style of convergence in technology because it is more than a lowest common denominator – it combines the strengths of different approaches. I hope that ABAP SOAP designtime will be as easy to use as RFC in the future – but perhaps this is only a dream.

 

But will this solve connectivity problems? This is a difficult question because it points to questions of systems’ architecture. When writing these sentences I hope no one will start a religious debate about REST. The reason is that I agree that SOAP and related standards are much too complex. Nevertheless I mastered many integration projects and found solutions for all SOAP related problems – but the most difficult part have been complex A2A interfaces and terrible implementations in backend systems that contained implicit and sometime shard-coded process chains and trees that made integration really difficult. IMHO we need integrations patterns for interface design, interface implementation and design of integration processes for every connectivity type, but this will be a topic for another blog.

Are Your ABAP Systems Healthy? Give them a Regular Checkup with SOA Manager

$
0
0

Those of you who attend your doctor for regular checkups are possibly more likely to live better and longer. And what about your peace of mind, at least for a short period after the checkup, knowing that everything is in order? Those little niggles or aches and pains - it is good to know that they are not something horrible about to take you down. And if something is discovered, is it not better to know and do something about it?

 

Unlike going to the doctor, there is nothing to fear with the health checks in SOA Manager. They only check for things that are fixable.

 

Health Checks from SOAMANAGER

 

When your Web services landscape is up and running, it is easy to forget that there are a lot of underlying things that need to be just right.

 

Connecting two ABAP systems is not difficult when everything is set up correctly. But if your connection is not working, you can spend a lot of time looking in the wrong place for the problem. A successful connection depends on diverse settings in the systems and clients involved.This is where health checks come in. Some possible uses are:

 

  • A previously working connection has failed. Is the landscape healthy?
  • You are adding a system to the landscape. Is it set up correctly for connectivity?
  • You are configuring a new connection and something is wrong. Is it the landscape?
  • You have upgraded a system. Is there an extra setting perhaps that you do not know about?
  • Maybe you just want to check on a regular basis that everything is in order. For example, there may be very little traffic between two systems so you do not get advance warning that something is wrong before that critical call is made.

 

If the health checks are in order, you can focus your troubleshooting activities on other typical problems such as incompatible WSDLs, security values, and so on.

 

Some checks are client-specific and some are not. From a central system you can execute health checks in selected managed systems and clients.The checks are documented in the Options screen. For some errors, you have the option to get SOA Manager to do an automated repair for you.

 

Example

 

Do you know all the ICF root nodes that are relevant for Web services? Of the top of my head I don’t. So let us take an example where one of these nodes is deactivated.

 

I use transaction SICF to deactivate the service scs.

 

sicf.gif

 

Now I go to transaction SOAMANAGER and find the Tools tab where the Health Check function is found.

 

hc_01.gif

 

I click on Health Check and then the Options button.

 

hc_02.gif

 

I am only interested in the ICF nodes so I deselect the others.

 

hc_03.gif

 

I click OK and then the Run button.

 

hc_04.gif

 

For this demonstration I am only interested in the local system, so I select that and press OK.

 

hc_05.gif

 

I expand to see more details and find the Repair button.

 

hc_06.gif

 

I click the Repair button.

 

hc_07.gif

The repair has worked but the health check has not been rerun.

 

hc_08.gif

 

So I display the Health Check Run screen again.

 

hc_09.gif

I click the Rerun Repaired button.

 

hc_10.gif

 

Now everything is green.

 

hc_11.gif

 

And in transaction SICF I can see that the relevant node has been activated.

 

sicf_2.gif

 

Background Jobs

 

If you want to run health checks automatically and periodically, you can schedule a background job with the well-known transaction SM36. The program to use in this case is SRT_CFG_HEALTH_CHECK. The results can be displayed in SOAMANAGER as normal, but you also have the option for them to be sent in an email.

 

srt_cfg_health_check.gif

Conclusion

 

Health checks from SOA Manager, or as background jobs, could save you a lot of frustration when working with Web services.

Trusted RFCs are not working

$
0
0

In cases where Trusted RFCs are not working, the easiest solution is to delete the trusted relationship and re-build it. However, before doing that, please make sure that you have SAP Note 1491645 implemented in both systems of the trusting relationship.

 

In summary:

  1. Delete the affected destinations in SM59 and SMT1.
  2. Re-create the trusted relationship:

 

NOTE: Make sure that user being used to re-create the trusted relationship has enough authorization. Even if user has SAP_ALL, notice that S_RFC and S_RFCACL authorizations are also required.

 

I hope this helps.

 

Best Regards,

Guilherme de Oliveira.

How to create SAPSLDAPI RFC in ECC towards SLD

$
0
0

Steps to create SAPSLDAPI

 

1. Goto SM59 Tcode.

2. In node TCP/IP Connections (T), Create.

3. RFC Destination Name : SAPSLDAPI

4.   In "Technical Settings" Tab,

    - Choose the option "Registered server Program"

    - Program ID : SAPSLDAPI_<SID>

    - Choose "Default Gateway Value"

    - In CPI-C timeout, Choose "Default Gateway Value"

    - In Gateway Options, Gateway Host : <Host Name>

                                        Gateway Service: SAPGW<Instance number>

5. Save.

6. Click on "Connection Test" button.

 

Note<SID> - Instance name of the PI/SolMan system hosted.

           <Host Name>  - Server Name where PI/SolMan system hosted.

            <Instance Number>  - instance number of PI/SolMan system.

                                                    Example : 00

ALE-Monitoring with WLF_IDOC

$
0
0

What is WLF_IDOC?

 

SAP has released a new transaction for monitoring IDOCs with EhP5 SP10 and EhP6 SP07 (SAP_APPL application component).

It's a nice transaction which is very interesting for people who have to deal with IDOCs in production environments. It combines the functions of BD87, WE02 and WE05 in a more modern UI than you know it from the older transactions:

 

  • overview
  • IDOC detail view 
  • change status directly without using program RC1_IDOC_SET_STATUS
  • compare different IDOCs with each other
  • copy IDOCs (and remove segments)
  • send IDOC to another system using RFC (not tried yet due to missing authorizations)

 

This screenshot should give a small impression (the toolbar icons realize the above mentioned functionality):

Screenshot_IDOC_Overview_WLF_IDOC.PNG

If you are used to BD87 or WE02 then you can easily use WLF_IDOC.

 

How do I get it?

 

If you're interested in how to make it easier for your users to handle ALE-monitoring have a look at SAP note 1724644 "IDOC Monitor". You cannot import the new monitoring tool by using SNOTE - you have to upgrade your support package level.

 

First impressions?

 

At first maybe you run in trouble with missing release information in IDOC segments (error message EA 257 in status bar if you klick on one IDOC) - therefore you can refer SAP note 1910051.

 

What's missing?

 

Due to some minor issues (like short dumps) I have not seen all functionality provided by the tool yet. But on a first look I couldn't find a funtion to display IDOCs as XML (like in an IS-U specific transaction called EDATEXMON01, there you can display all IDOCs as XML) which makes it easier to find values (Strg + F) and get an comprehensive view on the IDOC.

 

As soon there are new technical expertise findings I will update this blog.

Viewing all 68 articles
Browse latest View live


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