Showing posts with label sap. Show all posts
Showing posts with label sap. Show all posts

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.

Wednesday, November 24, 2010

Sapping up your Java Code - Part 1: The connection

SAP is an Enterprise Resource Planning system which controls a large market share. In many big companies SAP is the backbone that provides and relates information from most, if not all, departments. Having so much data, being able to access that information from outside the system becomes imperative. At the company I work for, we use Adobe LiveCycle to create paperless workflows that send and retrieve information from the SAP system. This enables our users to have a clear, concise... pretty... interface into SAP for common tasks. We are currently supporting 1000 users with this method. (Also, it only uses up one license on the SAP side). So, how does it work...


Let's talk about setting up a connection to SAP. Well, the first thing that you need is the SAP JCo. This is not freely available to everyone, you need an account at the SAP Service Marketplace. This site very poorly laid out, and it might be hard to find... I'd include a link right to the page, but I'm not sure that it would be reliable. I recommend doing a search for "JCo Download" and then when that search fails to return anything, click on the "Documents" section. You will have multiple options available for download, Windows/Linux/etc... pick whichever one applies to you. Make sure you add the jar and dll/so to your classpath properly. This post only talks about JCo3, I had worked with JCo2 previously but it wasn't very reliable under large amounts of processing, so I'd recommend going straight to JCo3.

Wahoo... we have the SAP JCo, now, how do we connect? The easiest way is not very efficient at all. In fact, it's a pretty dumb way to do it. The first thing you'll do is create a Properties object in your java code. You'll then populate it with some data, and then you'll write it to a file with the extension ".jcoDestination." You then tell JCoDestinationManager to get the destination by providing it the file name without the extension, here, let me show you some code:

//Other Class definition up here
...

//Don't just throw the exception, do actual error handling, this is just for
//demo sake

private JCoDestination makeConnection() throws Exception {
//Make the properties object
Properties p = new Properties();
p.setProperty(DestinationDataProvider.JCO_ASHOST, host);
p.setProperty(DestinationDataProvider.JCO_SYSNR, systemNumber);
p.setProperty(DestinationDataProvider.JCO_CLIENT, client);
p.setProperty(DestinationDataProvider.JCO_USER, userName);
p.setProperty(DestinationDataProvider.JCO_PASSWD, password);
p.setProperty(DestinationDataProvider.JCO_LANG, language);
p.setProperty(DestinationDataProvider.JCO_POOL_CAPACITY, poolSize);
p.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT, poolPeakSize);

//Write it out to a file... because file I/O is so nice
FileOutputStream fos = new FileOutputStream("sapconn.jcoConnection");
toProp().store(fos, "SAP System Connection");
fos.close();

//And read it back in...
JCoDestination destination = JCoDestinationManager
.getDestination("sapconn");
return destination;
}


As you can see it's a lot of pointless work to get the connection object. you could circumvent much of this by saving the first jcoDestination file that you create and re-using for every time you need to make your connection. Nonetheless, I think SAP made a very poor design choice when they limited the creation of "Destinations" to file I/O. There is an alternative to this method which is probably much more efficient, but also much more work. It involves implementing the DestinationDataProvider, registering it with the SAP JCo, and then creating the Destination.

The JCoDestination object is not quite a connection to SAP, but it's the closest thing you'll get. The JCoDestination is essentially a pointer to the connection pool. When you execute anything that needs to contact SAP it uses a JCoDestination to see exactly where it should be going. Now, let's talk about those properties we were just setting.

Table 1: SAP Configuration Properties


Property NameDescription
JCO_ASHOSTThis is the host, or server, that is running your SAP instance.
JCO_SYSNRThe is the System Number for your SAP system. If you're strictly a Java programmer, you're probably best to just ask what this is from someone in your SAP team. As far as I can tell, this is generally used to segregate Development and Production systems.
JCO_CLIENTAgain, this is something you'll have to ask for. The client seems to be a way to segregate some functionality of the system. Such as, functionality for different plants.
JCO_USERThis is the user that your Java code will use to connect to the SAP system.
JCO_PASSWDAnd this is the password related to that user.
JCO_LANGThe Language you want to use for the connection. I use EN, for English.
JCO_POOL_CAPACITYThis setting is for performance. It determines how many connections to SAP it will open at a time. You'll have to play with this setting, I'd start at 50, or 100 for moderate user load.
JCO_PEAK_LIMITOn top of the capacity limit, there is a peak limit. The capacity is for normal operation, whereas the peak, is for those times when your server is being pounded by user requests. The peak limit will allow for extra connections to be created as needed, over the initial capacity. These connections will be destroyed as soon as they are not in use, where as the connections specified in the capacity setting, will remain. This setting should be something larger than the CAPACITY, but it is up to you to determine what value is best for your application.

And there you go, you're connected to SAP. From here you're going to need access to an RFC (Remote Function Call) if you really want to make the system do something. Coming soon will be a discussion on the JCoFunction object and how to use it.