Wednesday, December 1, 2010

Sapping up your Java Code - Part 2: Functionality

If you're coming in late to this series, you might want to first read Part 1, since you can't really do anything without it.

The JCoFunction

A JCoFunction is an object in Java that represents a Remote Function Call. That is, a function in SAP that is "remote enabled." If you don't know of any of these, ask your SAP Team, probably an ABAP developer, to provide one for you. Nothing on the Java side will work without a RFC in SAP. Here's how it works:

  1. The Java program connects to the SAP system (see Part 1)
  2. The Java Program requests the function from the SAP system.
  3. The SAP system returns the meta-data of the function which is then turned into a JCoFunction object.
  4. The Java program populates the fields of the JCoFunction
  5. The Java program sends the function to SAP to be executed
  6. SAP sends the data back from the SAP call and it gets shoved right back into the same object.
  7. The Java program reads the information out of the JCoFunction and does whatever is needed.

This is essentially how SAP JCo works to communicate between systems. There are other things that can be done, like having SAP call out to a remote Java system, but that is outside the scope of this post. The code to do all of this is quite simple, requiring only a few objects, and the JCoDestination that we learned about in the previous post. I told you that previous post would be important.

In our demo, we'll try to connect to an SAP system and retrieve information about a customer. The Function in SAP will be named "Z_GET_CUSTOMER." If you're new to SAP, you might ask, why the 'Z?' The 'Z' actually means that the function that is being called was created by the customer, the customer being your company. In some rare cases, they also use 'Y' but that is not very common. The structure of the data will be similar to this:
Import Parameters (to be sent to SAP):
   CUSTOMER_ID     INT       An ID that identifies the Customer in SAP
Export Parameters (to be retrieved from SAP):
   STATUS          STRING    'S' is successful
   NAME            STRING
   LOCATION        STRING

As you can see from our example structure, we're not expecting this call to do much, but at least it's something.

Retrieving the Function from SAP

In step 2 listed above, the Java application request the function from SAP. How do we accomplish this? Well, the first thing we need is the JCoDestination object. The JCoDestination is your connection to SAP. From it, you have access to the JCoRepository through getRepository(). This is our first step. Our next step is to retrieve the function, and this couldn't be simpler. All we need to do is request the JCoFunction from the Repository through getFunction(String functionName).

Boiling that last paragraph down into a succinct hierarchy, you get this:
JCoDestination provides
   - JCoRepository provides
      - JCoFunction

So now the code. Generally, when I do this, I perform all actions in one line. This makes the line slightly more complex, but the repository is only used (in most cases) for providing functions, and can be disregarded right afterwards, so it makes little sense to store it in a variable, even temporarily.

JCoDestination _destination = ...;
JCoFunction f = _destination.getRepository().getFunction("Z_GET_CUSTOMER");

This provides the JCoFunction to you, but it also does quite a bit more behind the scenes. The repository actually caches the meta-data in the background. This means that the next time you request the same JCoFunction, it takes less time because the repository already has the meta-data required for creating the JCoFunction.

Populating the JCoFunction

Providing data to SAP, is quite a simple procedure. All you need to do is set values in the ImportParameterList. The ImportParameterList is actually a JCoParameterList, which is also used for export, and has one method for setting all the values you require. To use this method, you provide the name of the field within the Import Parameters, in our case "CUSTOMER_ID." So we provide it our Customer ID, we'll just say 1.

JCoParameterList imports = f.getImportParameterList();
imports.setValue("CUSTOMER_ID", 1);

Here we see, setting a value with an int. But an int is only one type of data we might want to provide. Sure, we could say that auto-boxing in Java 5 makes this possible by wrapping it as an Integer object and supposing that the setValue just accepts objects, but that's not what has happened here. The truth is, that the SAP Programmers have overloaded the setValue method to accept pretty much anything you can throw at it. This means that, even in Java 1.4, you can provide all your import parameters by using this one function.

Executing the call on the SAP System

Assuming that you have all your security settings are correct, making the call should be as simple as calling the execute method on the function. To call this method you need to provide the JCoDestination again, but this is a minor matter.

f.execute(_destination);

Done. Nothing else to do to execute the function. There is no return value, which in my opinion is unfortunate. I would have preferred the separation of input and output into different objects, like the way SQL Statements provide a ResultSet, but that is not how SAP designed the JCo Connector.

Reading the Information

All the information that SAP returned will be held within your original JCoFunction. You'll access them from ExportParameterList, and in some cases, TableParameterList. ExportParameterList, is where all your data should reside, as is what SAP recommends; but depending on the version of SAP, and the age of your SAP Programmers, that may not be true. TableParameterList was where lists of data would be returned, like if we wanted to return multiple customers, but at some point, SAP allowed import and export parameters to be tables as well.

So, reading from the ExportParameterList is as simple as this:
String status = f.getExportParameterList.getString("STATUS");
if("S".equals(status)){
    System.out.println("Customer Name is " + f.getExportParameterList.getString("NAME"));
}else{
    System.err.println("No customer found");
}

And that is all, in this post, I've shown how to use our previously established JCoDestination to retrieve and execute a function. As a side note, the JCo Connector version 3 was leaps and bounds above JCo 2. JCo 2 suffered from an API that didn't makes sense and complexity that was not required.