Skip to Content
Disclaimer

Important: the mechanism described in this article is intended as an example only and should not be interpreted as a generally accepted or endorsed way of connecting to and working with a SAP back-end systems. In particular, additional security measures: securing access to the web application, securing transport of data obtained via RFC calls, even though not being addressed here, are absolutely essential to the real-world application. Another important consideration: the usage of a generic system user to connect to the back-end system and execute BAPIs, from outside an SAP managed landscape, is most likely a subject to a specific licensing restrictions. The same might be the case with the use of SAPUI5 libraries when the framework will be released.

Motivation

With appearance of front-end tools like SAPUI5 (pure JavaScript), there is an alternative to the development of SAP compliant applications using standard IDEs like the popular NetBeans platform. This blog post will describe the way to set up a simple application using NetBeans which uses SAPUI5 demo kit, Jersey RESTful service and SAP JCo technology to execute RFC calls on the back-end system. The application is built and tested via Maven using an embedded Tomcat server.

Prerequisites

  • Download and install locally a distribution of JCo connector available from SAP Marketplace which corresponds to your architecture. The code used here was tested with SAP JCo version 3.0.8 for Windows 7 (x64).
  • RFC enabled system user for connection to ABAP back-end system.
  • Familiarity with NetBeans platform and Maven build framework.
  • JavaScript libraries from SAPUI5 demo kit, the code is based on version 1.8.4.
Setting up

Create a simple Maven based web application project. In order to use SAP JCo libraries as Maven dependencies, install the sapjco JAR in your local repository. Here are the relevant contents of pom.xml file for the project.

<dependencies>
    <dependency>
        <!-- install sap-jco-3.0.8.jar in local repository ~.m2/repository/com/sap/sap-jco/3.0.8, for example -->
        <groupId>com.sap</groupId>
        <artifactId>sap-jco</artifactId>
        <version>3.0.8</version>
    </dependency>
    <dependency>
        <groupId>com.sun.jersey</groupId>
        <artifactId>jersey-json</artifactId>
        <version>1.9.1</version>
    </dependency>
    <dependency>
        <groupId>com.sun.jersey</groupId>
        <artifactId>jersey-server</artifactId>
        <version>1.9.1</version>
    </dependency>
</dependencies>
<build>
    <plugins>
           <!-- other plugins: compile, war -->
        <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.0</version>
            <configuration>
                <port>8080</port>
                <path>/</path>
            </configuration>
        </plugin>
    </plugins>
</build>

Notice that we are using Tomcat Maven plugin to launch the web application in an embedded instance of Tomcat server.

The JARs from SAPUI5 demo kit go to WEB-INF/lib directory.

JCo service

SAP Java Connector (JCo) infrastructure allows to connect to a back-end ABAP system from any Java client. First we need to implement com.sap.conn.jco.ext.DestinationDataProvider describing the necessary connection properties.

public class JCoDataProvider implements DestinationDataProvider {
    public static final String RFC_SYSTEM_ID = "BACK_END_SYSTEM";
    private DestinationDataEventListener evtListener;
    private Properties props;
    @Override
    public Properties getDestinationProperties(String name) {
        if (name.equals(RFC_SYSTEM_ID)) {
            if (props == null) {
                try {
                    props = new Properties();
                    props.load(getClass().getClassLoader().getResourceAsStream("jco.properties"));
                } catch (IOException ex) {
                    throw new RuntimeException("Cannot load JCo properties for destination " + name + ". " + ex.getMessage());
                }
            }
        } else {
            throw new IllegalArgumentException("Not configured for " + name);
        }
        return props;
    }
    @Override
    public boolean supportsEvents() {
        return true;
    }
    @Override
    public void setDestinationDataEventListener(DestinationDataEventListener dl) {
        this.evtListener = dl;
    }
}

This implementation just reads the properties from a flat properties file. See examples from the distribution of sapjco for more examples.

Out JCo manager then uses the specified data provider to connect to the back-end system and execute RFC calls using JCo API.

public class JCoManager {
    private static final String EXPORT_PARAMETERS_LIST = "export_parameters_list";
    private static final String TABLE_PARAMETERS_LIST = "table_parameter_list";
    public JCoManager() {
        if (!Environment.isDestinationDataProviderRegistered()) {
            Environment.registerDestinationDataProvider(new JCoDataProvider());
        }
    }
    private Map<String, JCoRecord> executeRfc(String bapi, Map<String, Object> inParams) {
        Map<String, JCoRecord> records = new HashMap<String, JCoRecord>();
        try {
            JCoDestination jcoDestination = JCoDestinationManager.getDestination(JCoDataProvider.RFC_SYSTEM_ID);
            JCoFunction jcoFunction = jcoDestination.getRepository().getFunction(bapi);
            if (jcoFunction == null) {
                throw new JCoRuntimeException(JCoException.JCO_ERROR_FUNCTION_NOT_FOUND, bapi);
            }
            //see if we need to set the input parameters
            if (inParams != null) {
                //see if the function accepts any input parameters
                JCoParameterList jcoInParams = jcoFunction.getImportParameterList();
                if (jcoInParams == null) {
                    throw new JCoRuntimeException(JCoException.JCO_ERROR_CONFIGURATION,
                            "JCo function " + bapi + " does not have any input parameters");
                }
                //loop through the supplied parameter map and set the function parametres
                for (Iterator<String> iter = inParams.keySet().iterator(); iter.hasNext();) {
                    String key = iter.next();
                    Object value = inParams.get(key);
                    try {
                        jcoInParams.setValue(key, value);
                    } catch (JCoRuntimeException e) {
                        throw new JCoRuntimeException(e.getGroup(), e.getKey(), "Problem setting input parameter "
                                + key
                                + " for bapi " + bapi + ". " + e.getMessage(), e);
                    }
                }
            }
            //execute call
            jcoFunction.execute(jcoDestination);
            //set the export parameters
            JCoParameterList jcoOutParams = jcoFunction.getExportParameterList();
            if (jcoOutParams != null) {
                records.put(EXPORT_PARAMETERS_LIST, jcoOutParams);
            }
            //set the table parameters
            JCoParameterList jcoTabParams = jcoFunction.getTableParameterList();
            if (jcoTabParams != null) {
                records.put(TABLE_PARAMETERS_LIST, jcoTabParams);
            }
        } catch (JCoException e) {
            throw new JCoRuntimeException(e.getGroup(), e.getKey(), e.getMessage(), e);
        }
        return records;
    }
    /**
     * Executes STFC_CONNECTION BAPI. Can be used to check JCo connection to the
     * back-end.
     */
    public String[] checkConnection(String requText) {
        String[] result = new String[2];
        Map<String, Object> inParams = new HashMap<String, Object>();
        inParams.put("REQUTEXT", requText);
        Map<String, JCoRecord> outRecords = executeRfc("STFC_CONNECTION", inParams);
        JCoRecord outParams = outRecords.get(EXPORT_PARAMETERS_LIST);
        result[0] = outParams.getString("ECHOTEXT");
        result[1] = outParams.getString("RESPTEXT");
        return result;
    }
}

For our example we’ll use STFC_CONNECTION BAPI for checking the connection to the back-end, it’s called from checkConnection() method.

Jersey service

Create a simple Jersey resource and an implementation of com.sun.jersey.spi.inject.InjectableProvider to inject our JCo service as the singleton.

Here is the annotation used by the injectable provider.

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SingletonComponent {
}

Here is the injectable provider itself.

@Provider
public class SingletonComponentInjectableProvider implements InjectableProvider<SingletonComponent, Type>, Injectable<Object> {
    private Type type;
    @Override
    public ComponentScope getScope() {
        return ComponentScope.Singleton;
    }
    @Override
    public Injectable getInjectable(ComponentContext ctx, SingletonComponent annot, Type type) {
        this.type = type;
        return this;
    }
    @Override
    public Object getValue() {
        Object obj = null;
        try {
            obj = ((Class) type).newInstance();
        } catch (InstantiationException ex) {
            throw new RuntimeException(ex);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(ex);
        }
        return obj;
    }
}

And here is the resource which uses our JCo service and exposes the results of RFC calls as JSON objects.

@Path("/")
public class FlightResource {
    @SingletonComponent
    private JCoManager jCoManager;
    @Path("/check/{echo}")
    @GET
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Map check(@PathParam("echo") String echo) {
        Map map = new HashMap();
        String[] response = jCoManager.checkConnection(echo);
        map.put("str1", response[0]);
        map.put("str2", response[1]);
        return map;
    }
}

Do not forget to configure Jersey servlet in web.xml file.

<servlet>
        <servlet-name>jersey-rest-ws</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>com.sun.jersey.config.property.packages</param-name>
            <param-value>ch.unil.demo.jco</param-value>
        </init-param>
        <init-param>
            <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
            <param-value>true</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>jersey-rest-ws</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>

SAPUI5 front-end and testing

Create a simple HTML page with JS view and controller which allows to check the connection to the back-end.

Here is the details of the controller. It uses a JQuery driven AJAX call to our Jersey resource.

sap.ui.controller("flight.search", {
    check: function(echo){
      this._doAjax("/check/"+echo, null, "GET", false).done(function(data){
          console.log("Check connection returned: ", data);
          sap.ui.commons.MessageBox.alert(data.str2, null, data.str1);
      }); 
    },
    _doAjax: function(path, content, type, async) {
        var params = {
            url: "/rest" + path,
            dataType: "json",
            contentType: "application/json",
            context: this,
            cache: false
        };
        params["type"] = type || "POST";
        if (content) {
            params["data"] = JSON.stringify(content);
        }
        if (async === false) {
            params["async"] = async;
        }
        return jQuery.ajax(params);
    }
});

Here is the view.

sap.ui.jsview("flight.search", {
    getControllerName: function() {
        return "flight.search";
    },
    createContent: function(ctrl) {
        var btn1;
        btn1 = new sap.ui.commons.Button({
            text: "Check"
        });
        btn1.attachPress(function(evt) {           
            ctrl.check("test");
        });
        return [btn1];
    }
});

We can run the server with mvn tomcat7:run command and access our application at localhost.

To report this post you need to login first.

5 Comments

You must be Logged on to comment or reply to a post.

  1. Felipe Antonio Maria

    Hi George!

    I’m trying to install sapjco3.jar driver under my local maven repository but getting no success.

    The depency configuration inside pom is like that:

    <dependency>

         <groupId>com.sap</groupId>

         <artifactId>sap-jco</artifactId>

         <version>3.0.11</version>

    <dependency>

    This suggests that I have a folder like %M2&\respository\com\sap\3.0.11 but also suggests that my jar have to be under this format: <artifactId>-<version> so, maven looks for sap-jco-3.0.11.jar.

    When I rename this file an exceptions is throwed saying that I cannot rename or repackge the original file sapjco3.jar.

    What I have to do to solve this?

    Is this possible to use sapjco3.jar with maven?

    Thanks and regards!

    (0) 
    1. Terje Sten Bjerkseth

      Hi Felipe. I’m seeing the same issue with 3.0.11:

      ExceptionInInitializerError JCo initialization failed with java.lang.ExceptionInInitializerError: Illegal JCo archive “sap-jco-3.0.11.jar”. It is not allowed to rename or repackage the original archive “sapjco3.jar”.  com.sap.conn.jco.rt.MiddlewareJavaRfc.<clinit> (MiddlewareJavaRfc.java:229)

      It should work fine on 3.0.10 (and probably below), though, so give that version a try instead. At least 3.0.10 works on my platform.

      (We’re trying to report the issue with .11 through our SAP partner, hopefully we’ll get back Maven compatibility in the future.)

      (0) 
    2. George Ushakov Post author

      Hi Felipe and Terje,

      I was not aware of this limitation, it worked with 3.0.8 at the time.

      Just in case the suggestion of Terje does not work for you, Filipe : maybe you know this, in Maven you can reference any jar locally as is (without renaming), for this you can use the system scope. Them the groupId, artifactId, and version does not matter, and you can use the original name of the jar.

      <dependency>

                  <groupId>does.notp</groupId>

                  <artifactId>matter</artifactId>

                  <version>here</version>

                  <scope>system</scope>

                  <systemPath>/any/path/on/the/system/sapjco3.jar</systemPath>

              </dependency>

      Hope this helps, good luck.

      George

      (0) 

Leave a Reply