SAP

Topics relating to SAP

September 9, 2016 All, Blog, News, SAP

Software Asset Management on SCN

SAM logo, 70PrcntBill Rojas from SAP Digital recently posted a blog on the SAP Community Network (SCN) about Software Asset Management. See what he has to say here.

August 21, 2016 All, Blog, News, SAP

Software Asset Management now on SAP Store

SAP Store logo We’re ecxited about Software Asset Management having been included on SAP Store – SAP’s platform to discover, download & buy SAP solutions, services & free trials from SAP & partners. Beautifully presented by SAP!

Want to download? Do it right here.
April 15, 2016 All, News, SAP

Software Asset Management achieves certification

SAPCerti_Business_Process_Outsourcing_CG10_C_r_p

 

RBG Applications solution has certified integration with SAP NetWeaver® on SAP HANA®

Software Asset Management Achieves Certification

With SAP NetWeaver® Running on SAP HANA®

CAPE TOWN, South Africa — April 11, 2016 — RBG Applications today announced that its Software Asset Management solution version 0.8.1 has achieved SAP-certified integration with the SAP NetWeaver® technology platform running on the SAP HANA® platform. The solution has been proven to interoperate with SAP NetWeaver running on SAP HANA, providing optimized management of bespoke software assets.

The SAP® Integration and Certification Center (SAP ICC) has certified that Software Asset Management 0.8.1 integrates with SAP NetWeaver using standard integration technologies to support customers in better managing their software development lifecycle.

“We are delighted to announce that Software Asset Management has certified integration with SAP NetWeaver running on SAP HANA,” said Christian Hasselbach, solution architect at RBG Applications. “The ability of Software Asset Management to interoperate with SAP NetWeaver powered by SAP HANA will prove highly beneficial to our current and future customers.”

During the course of IT projects and in continuous improvement thereafter, substantial capital expenditure is made for development of bespoke software solutions. The purpose of Software Asset Management is to help ensure a transparent, well-documented outcome of bespoke software development and in that way, to protect the investment made. Software Asset Management will help orchestrate business, project managers, functional consultants, team leads, testers, application support and technical resources in such way that supportable and maintainable software is produced on time.
Software Asset Management extends SAP Solution Manager for application lifecycle management in that it focuses on technical objects that are the outcome of bespoke software development rather than on processes surrounding the build process (ChaRM). In Software Asset Management, technical objects are contextualized to allow for best possible maintenance and continuous improvement during their life cycle.

About RBG Applications

RBG Applications is a boutique software development firm. We implement cloud-based and on-premise solutions for innovative applications with a focus on mobile user experience.

SAP, SAP NetWeaver, SAP HANA and other SAP products and services mentioned herein as well as their respective logos are trademarks or registered trademarks of SAP SE (or an SAP affiliate company) in Germany and other countries. See http://www.sap.com/corporate-en/legal/copyright/index.epx for additional trademark information and notices.
All other product and service names mentioned herein are the trademarks of their respective owners.

For more information:
Please drop us an e-mail to info@rbgapps.com

May 1, 2015 All, Blog, SAP

Taming function module RFC_READ_TABLE

I recently wanted to write a report that would compare data between different systems (e.g. customizing or transactional data). I found the function module “RFC_READ_TABLE”, which delivers data from other systems. The where-clause, which can easily be passed into the options parameter, is essential, especially when comparing transactional data, as one does not want to have all of the data returned in one call for performance reasons.

I soon stumbled upon a limitation. This standard function module does not properly support packed fields and everything was out. I was checking the internet and found this nice SDN thread, which describes a solution. However, it does not include enough detail. After playing around a bit, I think I have tamed the beast.

There were a couple of issues, mainly the one that the length of the fields differs accordingly to their type and the offset to know where to start looking for the next needed to consider the length as well.

  DATA lt_where    TYPE rsds_where_tab.               “ where clause for selection
  DATA lt_nametab1 TYPE STANDARD TABLE OF rfc_db_fld. “ will be left blank
  DATA lt_raw_tab  TYPE TABLE OF veri_raw.            “ buffer table with raw data
  DATA lt_field    TYPE TABLE OF x031l.               “ field description
  DATA lv_x        TYPE xstring.
  DATA lv_str      TYPE string.
  DATA lv_char     TYPE char2048.
  DATA lv_offset   TYPE roffset.
  DATA lv_length   TYPE outlength.

  FIELD-SYMBOLS <ls_raw_line> TYPE veri_raw.
  FIELD-SYMBOL <fld>          TYPE any.
  FIELD-SYMBOL <fld_tar>      TYPE any.

* Get raw data from a different system
  CALL FUNCTION RFC_READ_TABLE' DESTINATION p_dest
    EXPORTING
      query_table = p_tabnam
    TABLES
      options     = lt_where
      fields      = lt_nametab1
      data        = lt_raw_tab
    EXCEPTIONS
      others      = 7.

    IF sy-subrc <> 0.
      MESSAGE ID sy-msgid TYPE 'E' NUMBER sy-msgno
              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4 DISPLAY LIKE 'I'.
    ENDIF.

*   Get table description e.g. from target system or current system, here current system is sufficent
    lv_tabnam = p_tabnam.
    CALL FUNCTION 'DD_GET_NAMETAB'
      EXPORTING
        tabname   = lv_tabnam
        get_all   = 'X'
      TABLES
        x031l_tab = lt_field
      EXCEPTIONS
        others    = 0.

*   Loop over the raw data needs to be done because packed fields are giving hassles
    LOOP AT lt_raw_tab ASSIGNING <ls_raw_line>.

*     get the string for the raw data
      lv_x = <ls_raw_line>.
      CALL FUNCTION 'HR_RU_CONVERT_HEX_TO_STRING'
        EXPORTING
          xstring = lv_x
        IMPORTING
          cstring = lv_str.

*     Move to character string
      lv_char = lv_str.

*     Clear some variables
      CLEAR <line2>.
      CLEAR lv_offset.

*     Loop over each field of the table
      LOOP AT lt_field ASSIGNING <field>.

*       Assign the target field
        ASSIGN COMPONENT <field>-fieldname OF STRUCTURE <line> TO <fld_tar>. “ <line> is a fieldsymbol which is assigned to a structure corresponding to the table

*       Determine the length of the field accordingly to type
        IF <field>-exid eq 'P'.         “packed field
          lv_length = <field>-digits.
        ELSEIF <field>-exid eq 'b'.     “integer field
          lv_length = <field>-exlength.
        ELSE.
          lv_length = <field>-dblength.
        ENDIF.

        IF lv_length EQ 0. “don’t processes if length is zero e.g. include description
          continue.
        ENDIF.

*       Get the field value
        ASSIGN lv_char+lv_offset(lv_length) TO <fld>.

*       Assign to target field
        <fld_tar> = <fld>.
*       Adjust length for next field
        ADD lv_length TO lv_offset.

      ENDLOOP.

*     Append line to compare table
      APPEND <line> TO <tab>.

    ENDLOOP.

Please note: The function module itself does an authority check and from my perspective it is the responsibility of the developer and administrator to ensure that the RFC destinations are set up properly, so that a logon is enforced. Otherwise this function module can be abused to read data from a system where the user is not supposed to poke around…

April 25, 2015 All, Blog, SAP

OpenUI5 Meets Google App Engine

This article was originally posted to the SAP Developer Center on Jan 26, 2014

I have been playing around with Google App Engine recently and, because the SAPUI5 library has recently gone open source, I have decided to introduce the two together and share my experiences. In order to do so, I have created a basic CRUD application that will manage Employee information. You can view the entire example on GitHub, this blog post will just showcase the most relevant pieces to shorten the post.

Backend

Google App Engine is a cloud computing platform as a service for developing and hosting web applications through Google’s infrastructure.

I am making use of Java and JDO to access the datastore (there are many other ways of achieving this on Google App Engine).

Employee JDO Class

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
public class Employee {

// ...
// Private persistent class attributes
// ...

public Employee(String firstName, String lastName, String phoneNumber,
    String cellNumber, String email, String idNumber, String country,
    String city) {

  this.key = KeyFactory.createKey(Employee.class.getSimpleName(), email);
  this.firstName = firstName;
  this.lastName = lastName;
  this.phoneNumber = phoneNumber;
  this.cellNumber = cellNumber;
  this.email = email;
  this.idNumber = idNumber;
  this.country = country;
  this.city = city;

}

// ...
// Public Getters and Setters
// ...

I have made use of the Google Cloud Endpoint Service to easily generate my web backend API and JavaScript client libraries. I have decided to only post the method and API access names here for readability and to shorten the blog, you can view the complete demo on Github.

Employee Endpoint Class

@ApiMethod(name = "listEmployee")
public CollectionResponse listEmployee(@Named("cursor") String cursorString,
    @Named("limit") Integer limit)

@ApiMethod(name = "getEmployee")
public Employee getEmployee(@Named("email") String email)

@ApiMethod(name = "insertEmployee") // Update employee is the same
public Employee insertEmployee(
  @Named("firstName") String firstName,
  @Named("lastName") String lastName,
  @Named("phoneNumber") String phoneNumber,
  @Named("cellNumber") String cellNumber,
  @Named("email") String email,
  @Named("idNumber") String idNumber,
  @Named("country") String country,
  @Named("city") String city )

@ApiMethod(name = "removeEmployee")
public void removeEmployee(@Named("email") String email)

By making use of the Google Plugin for Eclipse, I am easily capable of generating client libraries for iOS, Android and specifically JavaScript.

Connecting to the backend

In order to access my endpoints and use them in my OpenUI5 JavaScript application, I need to call the Google APIs JavaScript Client Library and load the endpoint.

I have created a custom OpenUI5 class that will take care of loading the client library and allow easy access throughout the application:

Endpoint Loader – Custom Class

jQuery.sap.declare("Endpoint");
jQuery.sap.require("sap.ui.base.ManagedObject");
sap.ui.base.ManagedObject.extend("Endpoint", {
  metadata: {
    properties: {
      "root": {type: "string", defaultValue: "http://127.0.0.1:8888/_ah/api"},
      "gapiObject": "object",
      "library": "object",
    }
  },
  init: function() {
    // Gets called right after instantiation
  },
  successDialog: new sap.m.Dialog({
    title: "Endpoint Client Libraries Successfully Loaded.",
    leftButton: new sap.m.Button({
      text: "Close",
      state: sap.ui.core.ValueState.Success,
      press: function(oControlEvent) {
        if( oControlEvent.getSource().getParent().isOpen() ) {
          oControlEvent.getSource().getParent().close();
        }
      }
    }),
  }),
  createLibrary: function() {
    var gapis = this.getGapiObject();
    var successDialog = this.successDialog;
    var simpleForm = new sap.ui.layout.form.SimpleForm({
      content: [ new sap.ui.core.Title({ text: "Success!" }) ]
    });
    successDialog.addContent(simpleForm);
    var response = function(endpointName) {
      console.log(endpointName + " has been loaded.");
      simpleForm.addContent( new sap.m.Label({ text: "Successfully Loaded" }) );
      simpleForm.addContent( new sap.m.Text({ text: endpointName }) );
      if( !successDialog.isOpen() ) {
        successDialog.open();
      }
    };
    for( k = 0; k &lt; gapis.length; k++ ) {
      // Load the client libraries through googles api object
      // (Connects to the endpoint classes)
      gapi.client.load(gapis[k].name, gapis[k].version,
          response(gapis[k].name), this.getRoot());
      // Set the google api client to the library object
      // for easy access
      this.setLibrary(gapi.client);
    }
    return this.getLibrary();
  },
  getEndpoint: function(name) {
    return this.getLibrary()[name];
  }
});

The project follows the MVC principles as discussed in the developers guide. I have made a few changes to the application class and the way it is loaded in the index.html file.

Custom Application Class

jQuery.sap.declare("Application");
jQuery.sap.require("sap.ui.app.Application");
jQuery.sap.require("Endpoint");

sap.ui.app.Application.extend("Application", {

  init: function() {
    // Set global models...
  },

  main: function() {
    // Create app view and set to the root html element
    var root = this.getRoot();
    var application = sap.ui.jsview("application", "js.Application");
    application.placeAt(root);

    // Connect to Google App Engine Endpoints
    var library = new Endpoint({
      id: "clientEndpoint",
      gapiObject: [
        { name: "employeeendpoint", version: "v1" } // Allows for
        // multiple endpoints to be loaded
      ]
    }).createLibrary(); // Create and return the loaded client library

    // Attach the client library to the application for easy access
    application.setClientEndpointLibrary( library );
  }

});

Index.html

<!DOCTYPE html>
<html lang="en">
<head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0,
        maximum-scale=1.0, user-scalable=no">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
       <title>OpenUI5 On Google App Engine</title>
      <script id="sap-ui-bootstrap"
            src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
            data-sap-ui-theme="sap_bluecrystal"
            data-sap-ui-libs="sap.ui.commons, sap.m"></script>
      <script>
            // Application Content...
            sap.ui.localResources("js");
            // Register Module Paths
            jQuery.sap.registerModulePath("Application", "Application");
            jQuery.sap.registerModulePath("Endpoint", "Endpoint");
      </script>
      <script type="text/javascript">
            var initializeGapi = function() {
                 // Launch Application...
                 jQuery.sap.require("Application");
                 var oApp = new Application({ root: "content" });
            }
      </script>
      <script type="text/javascript"
        src="https://apis.google.com/js/client.js?onload=initializeGapi">
      </script>
</head>
<body class="sapUiBody">
      <div id="content"></div>
</body>
</html>

I have incorporated the loading of the endpoint classes within the OpenUI5 application in order to reduce the time the user has to wait for the client library to load. If I had not done so, the user interface would load first and, depending on where the Google API load function is called, the data would not be available to query, leaving the user with an application that contains empty data.

Frontend – The Application

When the application is started and the main function is called, the endpoint classes are loaded and a message is displayed to the user. In this case I have just called a dialog to show that the endpoints have been loaded, but I have not included a check to see whether or not it was actually successful, despite calling it the “successDialog”.

openui5-meets-google-app-engine-01

In the CreateEmployee.controller.js I am assigning the loaded employeeEndpoint to the controller and calling a method that will use the InsertEmployee method of the endpoint class:

Creating an Employee

onBeforeRendering: function() {
  // Assign the employee endpoint to this controller to use
  this.employeeEndpoint = this.getView().getParent().getParent()
      .getParent().library.employeeendpoint;
},

I have called the endpoint library from the application by navigating through the relationships. There might be a better method of achieving this and I hope to improve on this solution in time, I welcome any suggestions and feedback.

save: function() {
  var resultDialog = this.resultDialog;

  // Inputs
  var firstName = this.getView().firstName;
  var lastName = this.getView().lastName;
  var city = this.getView().city;
  var country = this.getView().country;
  var phoneNumber = this.getView().phoneNumber;
  var cellNumber = this.getView().cellNumber;
  var email = this.getView().email;

  var clearInputs = function() {
    firstName.setValue("");
    lastName.setValue("");
    city.setValue("");
    country.setValue("");
    phoneNumber.setValue("");
    cellNumber.setValue("");
    email.setValue("");
  }

  var employee = {
    firstName: firstName.getValue(),
    lastName: lastName.getValue(),
    city: city.getValue(),
    country: country.getValue(),
    phoneNumber: phoneNumber.getValue(),
    cellNumber: cellNumber.getValue(),
    email: email.getValue()
  };

  this.employeeEndpoint.insertEmployee(employee).execute(function(response) {
    var resultMessage = new sap.m.Text({});

    if( !response.code ) { // No Error
      if( !resultDialog.isOpen() ) {
        resultDialog.setTitle("Success");
        resultDialog.setState(sap.ui.core.ValueState.Success);
        resultMessage.setText("Successfully saved the employee to the datastore.");
        resultDialog.addContent(resultMessage);
        resultDialog.open();

        clearInputs();
      }
    } else { // Error
      if( !resultDialog.isOpen() ) {
        resultDialog.setTitle("Error");
        resultDialog.setState(sap.ui.core.ValueState.Error);
        resultMessage.setText(response.message);
        resultDialog.addContent(resultMessage);
        resultDialog.open();

        clearInputs();
      }
    }
  });
}

openui5-meets-google-app-engine-02

openui5-meets-google-app-engine-03

Reading Employees

listEmployees: function() {
  var controller = this;
  var listEmployee = this.getView().listEmployee;

  this.employeeEndpoint.listEmployee().execute(function(response) {
    var jsonModel = new sap.ui.model.json.JSONModel(response);
    listEmployee.setModel(jsonModel);

    var template = new sap.m.ObjectListItem({
      title: "{key/name}",
      type: sap.m.ListType.Navigation,
      press: [controller.toGetEmployee, controller]
    });

    listEmployee.bindItems("/result/items", template);
  });
}

openui5-meets-google-app-engine-04

The data is stored in Google App Engines Datastore as shown below:

openui5-meets-google-app-engine-05

Deleting Employees

deleteEmployee: function() {
var employee = {
email: this.getView().email.getText()
};

this.employeeEndpoint.removeEmployee(employee).execute(function(response) {
// Response...
});

The OpenUI5 Library is still so young and yet so feature-rich, it excites me to see what other people are, and have already, come up with. This is my first blog post on SCN and as I have mentioned before, I welcome all feedback and suggestions and even if it is just a discussion on other technologies, I would love to engage in them with you.

Complete solution can be found on GitHub.