Skip to Content
Author's profile photo Alvaro Tejada Galindo

Calling R from the ERP – A dirty little hack

Since I joined SAP around 2 years ago, I simply stopped using ABAP…even when I use it for almost 11 years when I was a consultant…

A week ago, I was thinking about writing a new blog…something nice…some hacky…something that would allow me to just rest and don’t blog for the rest of the year…

I thought about ERP and R…while some things have been done already like Analytics with SAP and R from Piers Harding. I wanted to actually call R from the ERP instead of the ERP from R -;)

While thinking about this…I thought the best way to do it would be to reuse something that I before on my blog Consuming R from SAP Mobile Platform where I used Rook on Heroku to create a REST page that could be consumable…

So…for this particular blog…I wanted to something simple…call R, generate a graphic and show it on my ABAP program…here’s a small diagram to get things a little bit more clear…

ERP_Rook.jpg

Of course, when I tried this the first time…it failed…miserably…simply because the current implementation of Rook on Heroku doesn’t have graphic capabilities on it…so it was impossible to actually generate a graphic…

Let’s get our hands dirty and let’s separate this blogs into steps so it become easier to handle 🙂

Compile R with graphic capabilities on Heroku

This step was really a pain…it took me almost a week to get it done…and funny enough the rest of the steps took me about 6 hours…anyway…let’s keep going…

I started by looking at this two awesome projects on Github…rookonheroku and heroku-buildpack-r-build-r I took some chips from both and then create my own script and steps…

If you don’t have Heroku Tool Belt installed…go ahead and grab it…Log into Heroku and do the following…

Steps to install R on Heroku with graphic capabilities

mkdir myproject && cd myproject

mkdir bin

echo “puts ‘OK'” > config.ru

echo “source ‘http://rubygems.org’\n gem ‘rack'” > Gemfile

#Open your project folder and modify the Gemfile in Notepad…replace the “\n” by an actual line break.

bundle install

git init . && git add . && git commit -m “Init”

heroku apps:create myproject –stack=cedar

git push heroku master

#Copy and paste the content of my installR.sh file into your project folder, also copy the files features.h and features-orig.h from heroku-buildpack-r-build-r)

git add . && git commit -am “message” && git push heroku master

heroku ps:scale web=0

heroku run bash

cd bin/

./installR.sh

Here’s the code for the installR.sh script…

installR.sh

#!/bin/bash

# NOTE: Heroku Cedar Stack

#  gcc needs to be 4.3

# See http://cran.r-project.org/doc/manuals/R-admin.html for details on building R

## HELPERS

function download() {

  if [ ! -f “$2” ]; then

    echo Downloading $2…

    curl $1 -o $2

  else

    echo Got $2…

  fi

}

function build() {

  echo ———————————————————————-

  echo Building $@…

  echo ———————————————————————-

  echo

  pushd $1

    ./configure –prefix=$vendordir/$2 ${@:3} && make && make install && make clean

  popd > /dev/null

  echo

  echo

  # add to libraries and pkg-config

  export LD_LIBRARY_PATH=”$vendordir/$2:$LD_LIBRARY_PATH”

  export PKG_CONFIG_PATH=”$vendordir/$2/lib/pkgconfig:$PKG_CONFIG_PATH”

}

## SCRIPT

set -e

r_version=”${1:-2.13.1}”

r_version_major=${r_version:0:1}

if [ -z “$r_version” ]; then

  echo “USAGE: $0 VERSION”

  exit 1

fi

basedir=”$( cd -P “$( dirname “$0″ )” && pwd )”

# create output directory

vendordir=/app/vendor

mkdir -p $vendordir

echo ======================================================================

echo Downloading and unpacking dependancies…

echo ======================================================================

# We need to install xz to be able to unzip the gcc package we’re going to download in a minute

curl http://gfortran.com/download/x86_64/xz.tar.gz -o xz.tar.gz

tar xzvf xz.tar.gz

# Get and unpack gcc-4.3 binary, including gfortran

curl http://gfortran.com/download/x86_64/snapshots/gcc-4.3.tar.xz -o gcc-4.3.tar.xz

./usr/bin/unxz gcc-4.3.tar.xz

tar xvf gcc-4.3.tar

# http://www.freetype.org/freetype2/

freetype_version=2.5.0

download http://www.mirrorservice.org/sites/download.savannah.gnu.org/releases/freetype/freetype-$freetype_version.tar.gz freetype-$freetype_version.tar.gz

tar xzf freetype-$freetype_version.tar.gz

# http://directfb.org/

directfb_version=1.2.9

directfb_ver_major=${directfb_version:0:3}

download http://directfb.org/downloads/Core/DirectFB-$directfb_ver_major/DirectFB-$directfb_version.tar.gz DirectFB-$directfb_version.tar.gz

tar xzf DirectFB-$directfb_version.tar.gz

# http://www.libpng.org/pub/png/libpng.html

libpng_version=1.2.50

download ftp://ftp.simplesystems.org/pub/libpng/png/src/libpng12/libpng-$libpng_version.tar.gz libpng-$libpng_version.tar.gz

tar xzf libpng-$libpng_version.tar.gz

# http://www.cairographics.org

pixman_version=0.29.4

download http://www.cairographics.org/snapshots/pixman-$pixman_version.tar.gz pixman-$pixman_version.tar.gz

tar xzf $basedir/pixman-$pixman_version.tar.gz

# http://www.freedesktop.org/software/fontconfig

fontconfig_version=2.9.0

download http://www.freedesktop.org/software/fontconfig/release/fontconfig-$fontconfig_version.tar.gz fontconfig-$fontconfig_version.tar.gz

tar xzf $basedir/fontconfig-$fontconfig_version.tar.gz

# http://www.cairographics.org

cairo_version=1.9.14

download http://www.cairographics.org/snapshots/cairo-$cairo_version.tar.gz cairo-$cairo_version.tar.gz

tar xzf $basedir/cairo-$cairo_version.tar.gz

# http://www.pango.org

#pango_ver=1.35

#pango_version=$pango_ver.0

#download http://ftp.gnome.org/pub/GNOME/sources/pango/$pango_ver/pango-$pango_version.tar.xz pango-$pango_version.tar.xz

#tar xJf $basedir/pango-$pango_version.tar.xz

# R

download http://cran.r-project.org/src/base/R-$r_version_major/R-$r_version.tar.gz R-$r_version.tar.gz

tar xzf R-$r_version.tar.gz

# http://gcc.gnu.org/wiki/GFortran

gcc_version=4.3

download http://gfortran.com/download/x86_64/snapshots/gcc-$gcc_version.tar.xz gcc-$gcc_version.tar.xz

tar xJf $basedir/gcc-$gcc_version.tar.xz -C $vendordir

# patch gcc features.h file

# see http://permalink.gmane.org/gmane.comp.gcc.help/40166

mkdir -p $vendordir/gcc-$gcc_version/lib/gcc/x86_64-unknown-linux-gnu/$gcc_version/include-fixed

cp $basedir/features.h $vendordir/gcc-$gcc_version/lib/gcc/x86_64-unknown-linux-gnu/$gcc_version/include-fixed/features.h

# https://www.gnu.org/software/libc/

glibc_version=2.7

glibc_version_x=2.7ds1

download ftp://ftp.gunadarma.ac.id/linux/debian/pool/main/g/glibc/glibc_2.7.orig.tar.gz glibc_$glibc_version.tar.gz

tar xzf $basedir/glibc_$glibc_version.tar.gz -C $vendordir

tar xjf $vendordir/glibc-$glibc_version/glibc-$glibc_version_x.tar.bz2 -C $vendordir

echo ============================================================

echo Building dependencies…

echo ======================================================================

build “$basedir/freetype-$freetype_version” freetype

build “$basedir/DirectFB-$directfb_version” DirectFB

build “$basedir/libpng-$libpng_version” libpng

build “$basedir/pixman-$pixman_version” pixman

build “$basedir/fontconfig-$fontconfig_version” fontconfig

build “$basedir/cairo-$cairo_version” cairo

# copy over missing header files

cp $basedir/cairo-$cairo_version/src/*.h $vendordir/cairo/include

#build $basedir/pango-$pango_version pango

# build R

echo ============================================================

echo Building R

echo ============================================================

cd $basedir/R-$r_version/

export LDFLAGS=”-L$vendordir/gcc-$gcc_version/lib64″

export CPPFLAGS=”-I$vendordir/glibc-$glibc_version/string/ -I$vendordir/glibc-$glibc_version/time”

export PATH=”$vendordir/gcc-$gcc_version/bin:$PATH”

echo ———————————————————————-

echo LD_LIBRARY_PATH=$LD_LIBRARY_PATH

echo PKG_CONFIG_PATH=$PKG_CONFIG_PATH

echo ———————————————————————-

./configure –prefix=$vendordir/R –enable-R-shlib –with-readline=no –with-x=yes

make

cd /app/bin

#ln -s R-2.15-1/bin/R

ln -s R-2.13.1/bin/R

rm gcc-4.3.tar

rm glibc_2.7.orig.tar.gz

rm R.tar.gz

rm xz.tar.gz

rm -rf usr/

rm -rf gcc-4.3/bin

rm -rf gcc-4.3/lib

rm -rf gcc-4.3/libexec

rm -rf gcc-4.3/info

rm -rf gcc-4.3/man

rm -rf gcc-4.3/share

rm -rf gcc-4.3/include

rm glibc-2.7/*.tar.bz

cd bin/glibc-2.7/

rm -rf abilist/ abi-tags aclocal.m4  argp assert/ b* BUGS  C* c* d* e* F* g* h* i* I* l* m* M* N* aout/ LICENSES  n* o* p* P* R* r*

rm -rf scripts/ setjmp/ shadow/ shlib-versions signal/ socket/ soft-fp/ stdio-common/ stdlib/ streams/ sunrpc/ sysdeps/ sysvipc/

rm -rf termios/ test-skeleton.c timezone tls.make.c version.h Versions.def wcsmbs/ wctype/ WUR-REPORT

cd ../R-2.13.1

rm -rf src

rm Make*

rm -rf doc

rm NEWS*

rm -rf test

rm config*

rm O* README ChangeLog COPYING INSTALL SVN-REVISION VERSION

NEED etc

With that…you will have a full R installation with graphic capabilities…of course…you will need to delete all the garbage generated…but we will get there later…

Now…we need to install Rook…but we need version 1.0-2 so we need to download it and install it from the source…

Download Rook 1.0-2
Wget http://cran.r-project.org/src/contrib/Archive/Rook/Rook_1.0-2.tar.gz -o Rook_1.0-2.tar.gz

Then launch R…simply call it by saying…

Calling R
R

As it will launch the R environment…

Installing Rook

install.packages(/Rook_1.0-2.tar.gz, repos = NULL, type=”source”)

#Now we can simply quit R

q()

As I was lazy to work more on the script to delete those files automatically…basically…the “bin” folder should look like this…

Bin_Folder.jpg

And the “vendor” folder like this…

Vendor_Folder.jpg

Please remove the folders by using…

Removing folders

rm –rf name_of_folder

Once we’re done…we need to pack our stuff and sent it somewhere else, as Heroku is read only…once we log off…we lost everything…

Backup your work

tar -cvzf bin.tar.gz bin

tar -cvzf vendor.tar.gz vendor

scp bin.tar.gz me@myserver.com:/myproject/bin.tar.gz

scp vendor.tar.gz me@myserver.com:/myproject/vendor.tar.gz

#For scp you actually need a server…and I had my R on AWS…so I did used that…it might look like this…

scp -i XXX_X.pem bin.tar.gz root@XX.XX.XXX.XXX:/Blag/bin.tar.gz

Once we have everything saved…we can close the Heroku session and keep going…we can use WinSCP to copy our packed folders from our AWS server into our local project folder…uncompressed them and copy the following files from rookonheroku…

  • config.ru
  • demo.R
  • Procfile

Then…you can simply send everything back to Heroku…

Last push on Heroku

git add . && git commit -am “message” && git push heroku master

heroku ps:scale web=1

Of course…this is a lot of work…and I mean it…lot 🙂

So…I have upload the final compiled version to GitHub 😉 To get it…simply do this…

Getting Blagrook

git clone git:// github.com/atejada/BlagRook.git yourproject #(This will clone my Github and create a folder called yourproject to store the codes)

heroku create myapp #(Create an application for the Rook script)

git push heroku master #(This allows to pass everything from my yourproject folder to your Heroku account)

Create the code for the Rook application

On last thing we need to do…is to create the Rook application that it’s going to work on our Rook server…

demo.R

library(Rook)

newapp<-function(env){

   req<-Rook::Request$new(env)

   res<-Rook::Response$new()

   carrid_param<-c(req$params()$carrid)

   seats_param<-c(req$params()$seats)

   carrid_param<-strsplit(carrid_param,”,”)

   carrid_param<-c(carrid_param[[1]][1],carrid_param[[1]][2], carrid_param[[1]][3])

   seats_param<-strsplit(seats_param,”,”)

   seats_param<-c(as.numeric(seats_param[[1]][1]),as.numeric(seats_param[[1]][2]),

                            as.numeric(seats_param[[1]][3]))

   params<-data.frame(carrid_param,seats_param)

   bmp(“R_Plot.bmp”,type=c(“cairo”))

   dotchart(params$seats_param,labels=params$carrid_param,xlab=”Number of seats”,ylab=”Carriers”)

   dev.off()

   to.read = file(“R_Plot.bmp”, “rb”)

   x<-readBin(to.read, raw(),n=231488)

   hex<-paste(x, collapse = “”)

   res$write(hex)

   res$finish()

}

server = Rhttpd$new()

server$add(app = newapp, name = “summarize”)

server$start(listen=”0.0.0.0″, port=as.numeric(Sys.getenv(“PORT”)))

while(T) {

  Sys.sleep(10000)

}

In a few words what is happening here is simple…we receive the parameters “carrid” and “seats”…both came as a string with “,” so we use strsplit to separate them and then create a factor with the values. In the case of “seats” we need it to be numeric so we use the as.numeric function. Finally we create a data.frame combining “carrid” and “seats”. Using “Cairo” (That’s why we needed graphic capabilities on R) we create a Bitmap file which is going to be the result of creating a dotchart. Once it has been created, we read it, using the readBin function we get it’s Hexadecimal representation. Using the function paste and its collapse method, we put everything on a big string and send it back…easy as pie 😉

I’m sure that you will wonder how I came out with the number 231488 on the readBin line…well…I download a nice Hexadecimal Editor called HxD then I create the graphic on my local R and load it…the I went to the last line and took the Hex number…

Hex_Values.jpg

Using the calculator I simply changed the Hex value 38830 to decimal, multiply it by 16 and then add 16 more as the first line doesn’t count…simply and easy 😉

Create the function module ZUPLOAD_GRAPHICS_TABLE

This function module will allow us to upload the generated graphic into SE78.

ZUPLOAD_GRAPHICS_TABLE

FUNCTION ZUPLOAD_GRAPHICS_TABLE.

*”———————————————————————-

*”*”Local Interface:

*”  IMPORTING

*”     REFERENCE(OBJECT) TYPE  TDOBJECT DEFAULT ‘GRAPHICS’

*”     REFERENCE(NAME) TYPE  TDOBNAME

*”     REFERENCE(BTYPE) TYPE  TDBTYPE

*”     REFERENCE(ID) TYPE  TDID DEFAULT ‘BMAP’

*”     REFERENCE(RESOLUTION) TYPE  TDRESIDENT OPTIONAL

*”     REFERENCE(RESIDENT) TYPE  TDRESIDENT OPTIONAL

*”     REFERENCE(AUTOHEIGHT) TYPE  TDAUTOHGHT OPTIONAL

*”  TABLES

*”      T_BITMAP OPTIONAL

*”———————————————————————-

   TYPES: TY_BOOLEAN(1) TYPE C.

   TYPES: BEGIN OF TY_SBDST_CONTENT.

           INCLUDE STRUCTURE BAPICONTEN.

   TYPES: END OF TY_SBDST_CONTENT.

   TYPES: SBDST_COMPONENTS LIKE BAPICOMPON OCCURS 1,

          SBDST_PROPERTIES LIKE BAPIPROPER OCCURS 1,

          SBDST_SIGNATURE LIKE BAPISIGNAT OCCURS 1,

          SBDST_CONTENT LIKE BAPICONTEN OCCURS 1.

   DATA: T_BDS_CONTENT TYPE STANDARD TABLE OF

         TY_SBDST_CONTENT,

         WA_BDS_COMPONENTS TYPE LINE OF SBDST_COMPONENTS,

         L_BDS_PROPERTIES  TYPE SBDST_PROPERTIES,

         L_BDS_COMPONENTS  TYPE SBDST_COMPONENTS,

         T_STXBITMAPS TYPE STANDARD TABLE OF STXBITMAPS

         WITH HEADER LINE,

         WA_BDS_SIGNATURE TYPE LINE OF SBDST_SIGNATURE,

         L_BDS_SIGNATURE TYPE SBDST_SIGNATURE,

         L_BDS_CONTENT TYPE SBDST_CONTENT,

         WA_STXBITMAPS TYPE STXBITMAPS,

         WA_BDS_PROPERTIES TYPE LINE OF SBDST_PROPERTIES.

   DATA: W_BYTECOUNT TYPE I,

         W_COLOR(1) TYPE C,

         W_WIDTH_TW TYPE STXBITMAPSWIDTHTW,

         W_HEIGHT_TW TYPE STXBITMAPSHEIGHTTW,

         W_WIDTH_PIX TYPE STXBITMAPSWIDTHPIX,

         W_HEIGHT_PIX TYPE STXBITMAPSHEIGHTPIX,

         W_RESOLUTION TYPE STXBITMAPSRESOLUTION,

         W_BDS_BYTECOUNT TYPE I,

         W_TYPE TYPE STXBITMAPSTDBTYPE,

         W_DOCID TYPE STXBITMAPSDOCID.

   DATA: L_BDS_OBJECT TYPE REF TO CL_BDS_DOCUMENT_SET.

   CONSTANTS:

     C_TRUE  TYPE TY_BOOLEAN VALUE ‘X’,

     C_FALSE TYPE TY_BOOLEAN VALUE SPACE,

     C_BDS_MIMETYPE  TYPE BDS_MIMETP VALUE ‘application/octet-stream’,

     C_BDS_CLASSNAME TYPE SBDST_CLASSNAME VALUE ‘DEVC_STXD_BITMAP’,

     C_BDS_CLASSTYPE TYPE SBDST_CLASSTYPE VALUE ‘OT’.

   IF BTYPE = ‘BMON’.

     W_COLOR = C_FALSE.

   ELSE.

     W_COLOR = C_TRUE.

   ENDIF.

   CALL FUNCTION ‘SAPSCRIPT_CONVERT_BITMAP_BDS’

     EXPORTING

       COLOR                    = W_COLOR

       FORMAT                   = ‘BMP’

       BITMAP_BYTECOUNT         = W_BYTECOUNT

       compress_bitmap          = ‘X’

     IMPORTING

       WIDTH_TW                 = W_WIDTH_TW

       HEIGHT_TW                = W_HEIGHT_TW

       WIDTH_PIX                = W_WIDTH_PIX

       HEIGHT_PIX               = W_HEIGHT_PIX

       DPI                      = W_RESOLUTION

       BDS_BYTECOUNT            = W_BDS_BYTECOUNT

     TABLES

       BITMAP_FILE              = T_BITMAP

       BITMAP_FILE_BDS          = T_BDS_CONTENT

     EXCEPTIONS

       FORMAT_NOT_SUPPORTED     = 1

       NO_BMP_FILE              = 2

       BMPERR_INVALID_FORMAT    = 3

       BMPERR_NO_COLORTABLE     = 4

       BMPERR_UNSUP_COMPRESSION = 5

       BMPERR_CORRUPT_RLE_DATA  = 6

       OTHERS                   = 7.

   CREATE OBJECT L_BDS_OBJECT.

   WA_BDS_COMPONENTSDOC_COUNT  = ‘1’.

   WA_BDS_COMPONENTSCOMP_COUNT = ‘1’.

   WA_BDS_COMPONENTSMIMETYPE   = C_BDS_MIMETYPE.

   WA_BDS_COMPONENTSCOMP_SIZE  = W_BYTECOUNT.

   APPEND WA_BDS_COMPONENTS TO L_BDS_COMPONENTS.

   W_TYPE = ‘BSD’.

   SELECT SINGLE *

   INTO T_STXBITMAPS

   FROM STXBITMAPS WHERE TDOBJECT = OBJECT

                                   AND   TDNAME   = NAME

                                   AND   TDID     = ID

                                   AND   TDBTYPE  = BTYPE.

   IF SYSUBRC = 0.

     READ TABLE T_STXBITMAPS INDEX 1.

     W_DOCID = T_STXBITMAPSDOCID.

   ELSE.

     CLEAR W_DOCID.

   ENDIF.

   IF W_DOCID IS INITIAL.

     WA_BDS_SIGNATUREDOC_COUNT = ‘1’.

     APPEND WA_BDS_SIGNATURE TO L_BDS_SIGNATURE.

     CALL METHOD L_BDS_OBJECT->CREATE_WITH_TABLE

       EXPORTING

         CLASSNAME  = C_BDS_CLASSNAME

         CLASSTYPE  = C_BDS_CLASSTYPE

         COMPONENTS = L_BDS_COMPONENTS

         CONTENT    = T_BDS_CONTENT

       CHANGING

         SIGNATURE  = L_BDS_SIGNATURE

       EXCEPTIONS

         OTHERS     = 1.

     READ TABLE L_BDS_SIGNATURE INDEX 1 INTO WA_BDS_SIGNATURE

     TRANSPORTING DOC_ID.

     IF SYSUBRC = 0.

       W_DOCID = WA_BDS_SIGNATUREDOC_ID.

     ENDIF.

   ELSE.

     CALL METHOD L_BDS_OBJECT->UPDATE_WITH_TABLE

       EXPORTING

         CLASSNAME     = C_BDS_CLASSNAME

         CLASSTYPE     = C_BDS_CLASSTYPE

         DOC_ID        = W_DOCID

         DOC_VER_NO    = ‘1’

         DOC_VAR_ID    = ‘1’

       CHANGING

         COMPONENTS    = L_BDS_COMPONENTS

         CONTENT       = T_BDS_CONTENT

       EXCEPTIONS

         NOTHING_FOUND = 1

         OTHERS        = 2.

   ENDIF.

   WA_STXBITMAPSTDNAME     = NAME.

   WA_STXBITMAPSTDOBJECT   = OBJECT.

   WA_STXBITMAPSTDID       = ID.

   WA_STXBITMAPSTDBTYPE    = BTYPE.

   WA_STXBITMAPSDOCID      = W_DOCID.

   WA_STXBITMAPSWIDTHPIX   = W_WIDTH_PIX.

   WA_STXBITMAPSHEIGHTPIX  = W_HEIGHT_PIX.

   WA_STXBITMAPSWIDTHTW    = W_WIDTH_TW.

   WA_STXBITMAPSHEIGHTTW   = W_HEIGHT_TW.

   WA_STXBITMAPSRESOLUTION = RESOLUTION.

   WA_STXBITMAPSRESIDENT   = RESIDENT.

   WA_STXBITMAPSAUTOHEIGHT = AUTOHEIGHT.

   WA_STXBITMAPSBMCOMP     = ‘X’.

   INSERT INTO STXBITMAPS VALUES WA_STXBITMAPS.

ENDFUNCTION.


Create the ABAP program to make it all happen…

ZROOK

*&———————————————————————&*

*& Report  ZROOK                                             &*

*&———————————————————————&*

*& Author: Alvaro “Blag” Tejada Galindo               &*

*& Company: SAP Labs Palo Alto                       &*

*& Date: November 11, 2013                               &*

*& Modified on: November 18, 2013                     &*

*& Reason: Finish the code -;)                            &*

*&*******************************************************&*

REPORT  ZROOK NO STANDARD PAGE HEADING.

TYPES : BEGIN OF ty_bitmap,

         l(64) TYPE x,

         END OF ty_bitmap.

TYPES: BEGIN OF TY_GRAPHIC_TABLE,

        LINE(255) TYPE X,

        END OF TY_GRAPHIC_TABLE.

types: begin of ty_flights,

        carrid type sflightcarrid,

        seatsocc type sflightsseatsocc,

        end of ty_flights.

DATA: it_bitmap TYPE STANDARD TABLE OF ty_bitmap,

       i_po_data TYPE STANDARD TABLE OF string,

       i_bitmap TYPE STANDARD TABLE OF string,

       T_GRAPHIC_TABLE TYPE STANDARD TABLE OF TY_GRAPHIC_TABLE,

       t_flights type STANDARD TABLE OF ty_flights,

       bitmap type string,

       client TYPE REF TO if_http_client,

       container TYPE REF TO cl_gui_custom_container,

       lv_length   TYPE i,

       lv_content  TYPE xstring,

       lr_mime_rep TYPE REF TO if_mr_api,

       OUTXSTRING type xstring,

       URL(255) TYPE C,

       PICTURE TYPE REF TO CL_GUI_PICTURE,

       L_BYTECOUNT TYPE I,

       L_CONTENT TYPE STANDARD TABLE OF BAPICONTEN,

       GRAPHIC_SIZE TYPE I,

       bin TYPE xstring,

       conv TYPE REF TO cl_abap_conv_in_ce,

       response TYPE string,

       carrid type string,

       seats type string,

       seatsocc type string,

       param type string.

FIELD-SYMBOLS: <fs_bitmap> like line of i_bitmap,

                <fs_itbitmap> like line of it_bitmap,

                <fs_po_data> like line of i_po_data,

                <fs_flight> like line of t_flights.

START-OF-SELECTION.

   perform get_data.

   perform call_rook.

   perform create_graphic.

   perform show_graphic.

*&———————————————————————*

*&      Form  GET_DATA

*&———————————————————————*

FORM GET_DATA.

   select carrid sum( seatsocc ) into table t_flights

   from sflights up to 3 rows

   group by carrid.

   loop at t_flights ASSIGNING <fs_flight>.

     seatsocc = <fs_flight>seatsocc.

     CONDENSE seatsocc NOGAPS.

     CONCATENATE carrid <fs_flight>carrid into carrid SEPARATED BY ‘,’.

     CONCATENATE seats seatsocc into seats SEPARATED BY ‘,’.

   ENDLOOP.

   replace regex ‘\A,’ in carrid with space.

   replace regex ‘\A,’ in seats with space.

   CONCATENATE ‘/custom/summarize?carrid=’carrid ‘&seats=’ seats into param.

ENDFORM.                    ” GET_DATA

*&———————————————————————*

*&      Form  CALL_ROOK

*&———————————————————————*

FORM CALL_ROOK.

   CALL METHOD cl_http_client=>create

     EXPORTING

       host   = ‘blagrook.herokuapp.com’

       scheme = 1

     IMPORTING

       client = client.

   client->request->set_method( if_http_request=>co_request_method_get ).

   cl_http_utility=>set_request_uri( request = client->request uri = param ).

   client->send( ).

   client->receive( ).

   bin = client->response->get_data( ).

   conv = cl_abap_conv_in_ce=>create( input = bin ).

   conv->read( IMPORTING data = response ).

   client->close( ).

ENDFORM.                    ” CALL_ROOK

*&———————————————————————*

*&      Form  CREATE_GRAPHIC

*&———————————————————————*

FORM CREATE_GRAPHIC.

   bitmap = response.

   TRANSLATE bitmap TO UPPER CASE.

   CALL FUNCTION ‘SOTR_SERV_STRING_TO_TABLE’

     EXPORTING

       TEXT        = bitmap

       LINE_LENGTH = 128

     TABLES

       TEXT_TAB    = i_bitmap.

   loop at i_bitmap ASSIGNING <fs_bitmap>.

     APPEND INITIAL LINE TO it_bitmap ASSIGNING <fs_itbitmap>.

     <fs_itbitmap>l = <fs_bitmap>.

   ENDLOOP.

   CALL FUNCTION ‘ZUPLOAD_GRAPHICS_TABLE’

     EXPORTING

       OBJECT   = ‘GRAPHICS’

       NAME     = ‘BLAG’

       BTYPE    = ‘BMON’

       ID       = ‘BMAP’

     TABLES

       T_BITMAP = it_bitmap.

ENDFORM.                    ” CREATE_GRAPHIC

*&———————————————————————*

*&      Form  SHOW_GRAPHIC

*&———————————————————————*

FORM SHOW_GRAPHIC.

   CALL FUNCTION ‘SAPSCRIPT_GET_GRAPHIC_BDS’

     EXPORTING

       I_OBJECT       = ‘GRAPHICS’

       I_NAME         = ‘BLAG’

       I_ID           = ‘BMAP’

       I_BTYPE        = ‘BMON’

     IMPORTING

       E_BYTECOUNT    = L_BYTECOUNT

     TABLES

       CONTENT        = L_CONTENT

     EXCEPTIONS

       NOT_FOUND      = 1

       BDS_GET_FAILED = 2

       BDS_NO_CONTENT = 3

       OTHERS         = 4.

   CALL FUNCTION ‘SAPSCRIPT_CONVERT_BITMAP’

     EXPORTING

       OLD_FORMAT               = ‘BDS’

       NEW_FORMAT               = ‘BMP’

       BITMAP_FILE_BYTECOUNT_IN = L_BYTECOUNT

     IMPORTING

       BITMAP_FILE_BYTECOUNT    = GRAPHIC_SIZE

     TABLES

       BDS_BITMAP_FILE          = L_CONTENT

       BITMAP_FILE              = T_GRAPHIC_TABLE

     EXCEPTIONS

       OTHERS                   = 1.

   CALL FUNCTION ‘DP_CREATE_URL’

     EXPORTING

       TYPE    = ‘IMAGE’

       SUBTYPE = ‘BMP’

     TABLES

       DATA    = T_GRAPHIC_TABLE

     CHANGING

       URL     = URL.

   CREATE OBJECT container

     EXPORTING

       container_name =

       repid          = ‘SAPMSSY0’

       dynnr          = ‘0120’.

   container->set_top( 85 ).

   container->set_left( 120 ).

   container->set_width( 580 ).

   container->set_height( 580 ).

   CREATE OBJECT PICTURE

     EXPORTING

       PARENT = container.

   CALL METHOD PICTURE->LOAD_PICTURE_FROM_URL

     EXPORTING

       URL = URL.

   CALL METHOD PICTURE->SET_DISPLAY_MODE

     EXPORTING

       DISPLAY_MODE = PICTURE->DISPLAY_MODE_FIT_CENTER.

   WRITE .

ENDFORM.                    ” SHOW_GRAPHIC


When we run our program…we will call our Rook application on Heroku, which will receive the sum of seats for three carriers and then create a graphic, read it’s hexadecimal representation, give back to ABAP which will in turn create it on SE78 and then read it to show it on screen…now…you will not get the best resolution ever…and will assume it’s because “Cairo” doesn’t really support bmp files, or because the graphic is getting transported and changed all the way…so…it doesn’t look good…but heck! We’re calling R from the ERP…what else do you want? 😀

ERP_Rook_Graphic.jpg

It was really fun…so see you next year -;)

Blag.

Developer Experience.

Assigned Tags

      13 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Former Member
      Former Member

      Nice work Alvaro - I think some of what you have done here could be extended to integration with RShiny!

      Cheers,

      Piers Harding.

      Author's profile photo Alvaro Tejada Galindo
      Alvaro Tejada Galindo
      Blog Post Author

      Thanks Piers! Glad you like it 🙂 And sure...Shiny would be awesome to integrate... 😀

      Can't wait to see what can came up with 😉

      Greetings,

      Blag.

      Developer Center.

      Author's profile photo Christopher Solomon
      Christopher Solomon

      "Sorcery!!! Burn the witch!!!!"

      Great work!

      Author's profile photo Alvaro Tejada Galindo
      Alvaro Tejada Galindo
      Blog Post Author

      Thanks Chris! 😀

      Do you smell something burning? 😉

      Greetings,

      Blag.

      Developer Experience.

      Author's profile photo Former Member
      Former Member

      Hello Alvaro,

      It is an amazing blog! We are trying out something similar as an experiment. Since I'm from SAP background i really didn't understand the Heroku/Rook completely.

      We have an custom R algorithm (Input: Parameters & tables, Output: Table [data.frame..]) and need to call it from SAP ECC by passing the Input data. Could you please guide me on how to proceed?

      Below are my assumption for this requirement, please correct me.

      1. Additional R Server on linux (Reachable to SAP ECC)

      2. "Rook" as web server on R (which internally calls the custom R Algorithm)

      3. Using ABAP invoke the URL of Rook by passing the parameter..? (How to pass table?)


      Thanks & Regards,

      Chathia.

      Author's profile photo Alvaro Tejada Galindo
      Alvaro Tejada Galindo
      Blog Post Author

      Chathia:

      I used Heroku simply because on my AWS R Server...I couldn't make Rook work as a "real web server" as it was working only as a "Local Web Server"...also because I wanted to show something different 😉

      But yes...you're right...your R server plus "Rook" must be accessible from the ERP...so if you can make work on AWS or something else...then you're almost there 🙂

      In order to pass an ERP table to Rook...you need to something like I did...which is basically concatenate the fields of the table on a single line and then extract them on R...so if you have...

      Field A | Field B | Field C

          2           3A       45

          1           4C       30

      Then you need to pass....

      2,3A,45;1,4C,30

      You can separate the fields by "," and the records by ";"...

      Otherwise...there's no way to do it as of course ABAP Tables and R Data.Frames are not compatible...

      You pass your string to the Rook URL...and process it on R...

      Greetings,

      Blag.

      Development Culture.

      Author's profile photo Former Member
      Former Member

      Dear Alvaro,

      Thanks a ton for your advice..!

      Actually i might need to pass quite a few records (~50rows with 10columns) and since the URL has a limited length wud become a bottleneck.

      But instead i can generate a json/xml from ERP to Rook and also expect the Rook to send me back similarly. Hope Rook as a web server, can I do something like this?

      Also can you advice on the performance in terms of ERP to Rook etc(leaving the complexity of the algorithm & abap)? Can it be made live (Eg. in few seconds?)

      Regards,

      Chathia.

      Author's profile photo Alvaro Tejada Galindo
      Alvaro Tejada Galindo
      Blog Post Author

      Chathia:

      Well...R have some JSON packages so you might be able to manage it that way...however I have never done JSON with Rook...

      I haven't made many performance tests...but for the ones I did...it was really good...as everything...you might need to test it yourself 🙂

      Greetings,

      Blag.

      Development Culture.

      Author's profile photo Former Member
      Former Member

      Dear Alvaro,

      Finally we have setup a R Server with Rook with JSON packages. And could generate an URL which takes some parameter & returns output in JSON format..

      But when I try to connect the server from ABAP it is throwing the following error.

      1. ABAP is able to connect to the host & port "saprserver.xxx.com:8081" and the return message "Only NEWS and URLs under /doc and /library are allowed" is captured which is fine and it works the same if the url is called from browser too.


      2. But if the path (/custom/summarize?name=CHATHIA&age=30) is set to call the rook app, it does not work with ABAP and throws HTTP_COMMUNICATION_FAILURE. It looks like some kind of settings missing in SAP Server? Could you pls throw some ideas you have?

      Sample ABAP Code:

      PARAMETERS : p_name TYPE string ,

                    p_age  TYPE string,

                    p_rook TYPE string OBLIGATORY,

                    p_port TYPE string .

      DATA : lv_client TYPE REF TO if_http_client,

              lv_bin TYPE xstring,

              lv_conv TYPE REF TO cl_abap_conv_in_ce,

              lv_param TYPE string,

              lv_response TYPE string.

        CALL METHOD cl_http_client=>create

            EXPORTING

              host   = p_rook

              SERVICE = p_port

              scheme = 1

            IMPORTING

              client = lv_client.

          lv_client->request->set_method( if_http_request=>co_request_method_get ).

          CONCATENATE

                      '/custom/summarize?name=' p_name '&age=' p_age

                INTO lv_param.

      *   CALL METHOD lv_client->request->set_header_field

      *     EXPORTING

      *       name  = 'Content-Type'

      *       value = 'application/json; charset=UTF-8'.

          cl_http_utility=>set_request_uri(

             request = lv_client->request

             uri = lv_param

           ).

          lv_client->send( ).

          lv_client->receive( ).

          lv_bin = lv_client->response->get_data( ).

          lv_conv = cl_abap_conv_in_ce=>create( input = lv_bin ).

          lv_conv->read( IMPORTING data = lv_response ).

          lv_client->close( ).

          WRITE : lv_response(50),/,

                  lv_response+50(50).

      Regards,

      Chathia.

      Author's profile photo Alvaro Tejada Galindo
      Alvaro Tejada Galindo
      Blog Post Author

      Chathia:

      Why are you specifying a port on the cl_http_client call? Heroku apps doesn't come with ports as part of their URL's...

      Also...can you post the R code?

      Also...check this part

      CONCATENATE '/custom/summarize?name=' p_name '&age=' p_age INTO lv_param.

      It should be actually like this...

      CONCATENATE '/custom/summarize?name=''' p_name '''&age=' p_age INTO lv_param.


      Because p_name is a string and it should be called with enclosed within "".


      Greetings,


      Blag.

      Development Culture.

      Author's profile photo Former Member
      Former Member

      I didnt go with heroku, but directly we are hosting R + Rook on a Windows server which is in our LAN. So we got to pass the port number.. The port is kept open and so that we are able to call the url from any machine in the LAN..

      also we are able to access this url&port using browser from the Windows Server where SAP is installed .. so reaching url/port should not be a problem here but through SAP it is not connecting.. Is there an issue with security settings like STRUST config in SAP?

      I tried your code but still the same error..

      CONCATENATE '/custom/summarize?name=''' p_name '''&age=' p_age INTO lv_param.

      The R code is simple, it just reads the name, age and sends back the same packed in json format..!!

      library(Rook)

      library(rjson)

      myPort <- 8081

      myInterface <- "0.0.0.0"

      status <- -1

      if (as.integer(R.version[["svn rev"]]) > 59600) {

        status <- .Call(tools:::startHTTPD, myInterface, myPort)

      } else {

        status <- .Internal(startHTTPD(myInterface, myPort))

      }

      if (status == 0) {

        unlockBinding("httpdPort", environment(tools:::startDynamicHelp))

        assign("httpdPort", myPort, environment(tools:::startDynamicHelp))

        s <- Rhttpd$new()

        s$listenAddr <- myInterface

        s$listenPort <- myPort

        s$add(

          name ="summarize",

          app  = function(env) {

            req = Rook::Request$new(env)

            results = list()

            results$name = paste(req$params()$name, "-firstname", sep=" ")

            results$age = paste(req$params()$age, "-age", sep=" ")

       

            res = Rook::Response$new()

            res$write(toJSON(results))

            res$finish()

          }

        )

      s$start()

      s$browse("summarize")

      }

      Author's profile photo Alvaro Tejada Galindo
      Alvaro Tejada Galindo
      Blog Post Author

      I see...then if you call it from the browser it works fine, right? With and without the custom/customize URL...but in ABAP only works without the custom/customize...

      That's pretty weird...and sadly I don't have any server to use and test it... 🙁

      Greetings,

      Blag.

      Development Culture.

      Author's profile photo Former Member
      Former Member

      Hi Alvaro, After breaking my head for a week, I'm close to the issue. Hope you can help me out now 🙂 .

      - The R-JSON results from the code which i had posted earlier has the problem. It sends with duplicate "Content-Length" and SAP ICM throws the error while parsing. The browsers Chrome/Firefox does not treat it as error 🙂 .

      DuplicateContent_Length.png

      - SAP ICM Error log

      ABAP-ICM Monitor Error.png

      As a initial fix, I tried to use the library "RJSONIO" instead of "rjson" but still the response contains duplicate "Content-Length".

      Could you please help me in using the right R-JSON packages?

      Udpate: I guess it is not the problem with rjson, but with Rook. Coz even if send a sample html as output, it still shows duplicate "Content-Length".

            res$write("<h1>A Simple Web Application</h1>")

      Regards,

      Chathia.