[ Team LiB ] Previous Section Next Section

Server Programming in JCo

JCo provides the capability not only for the Java side to call SAP, but also the other way around, so that SAP can call Java. This would be very useful in cases in which immediate notification is necessary. For instance, when a special condition is applied in SAP, email must be sent to all Web-based customers residing on an external database. In this section, we discuss how to set up a JCo server and listen for incoming calls. The code for this section is located in the /server directory on the companion CD. A complete working version plus sources is included.

Creating a JCo Server

A JCo server must be configured internally in SAP. An SAP system cannot just call an external program without having the skeleton of the import, export, and table parameters defined within the system. Instead of creating our own custom RFC for this example, let's use an RFC that has already been defined: RFC_CUSTOMER_GET. This function can be accessed through SAP transaction SE37. It contains two optional input parameters and one table parameter. Figure 32.2 is a screenshot of the table dictionary object needed for our function. This information must be created within our server as a repository.

Figure 32.2. SAP data dictionary screen.

graphics/32fig02.jpg

Earlier in this chapter, we discussed how JCo automatically creates the repository object and populates it with the correct metadata. However, when implementing the JCo server, we have to define our own repository. This information should be defined as static and usually can be an inner class. In our example, I've defined a wrapper class with two internal classes:

  • Repository— Contains a new repository object that extends the basic repository and implements IRepository

  • Server— Extends a JCO.Server object and handles all the requests for our program

The wrapper class implements JCO.ServerExceptionListener to handle any exceptions that might be thrown. It also contains a simple main() method that calls the runServer() method to start the server. The following code shows how this works:


public class JCoServer implements JCO.ServerExceptionListener{

    protected static IRepository repository; //define my Repository
    JCO.Server server = null;         //define my Server
...
public static void main(String[] argv)
  {
    JCO.setTraceLevel(0);
    JCoServer myserver = new JCoServer();
    myserver.runServer();
  }

Creating a Repository

Next we define the inner class for our repository. This class is defined as static because we don't need to instantiate the data more than once. We need this class because we're defining our own repository for the server, and cannot use JCO.Repository, which is specifically used in client programs.


//Create a static repository to hold all metadata for our functions.
  static public class Repository extends 
     JCO.BasicRepository implements com.sap.mw.jco.IRepository {


    public Repository(String repname)
    {
      super(repname);
    }
  }

With the inner class Repository defined, we must now create the data for the repository. For performance reasons, we use a static block within the wrapper class. This code will be run only once: when the class is instantiated. The important JCo class needed to add the metadata is JCO.MetaData. This class has an overloaded addInfo() method that's used to add different types of metadata. In our case, we need to define two types of metadata:

  • Function interface data— Metadata that defines the import, export, and table parameters for the function

  • Structure definition data— Metadata that defines the underlying structure of the import, export, and table parameters

The addInfo() method for adding our function interface data contains these arguments:

  • name— Name of the field

  • type— JCo data type

  • length— Length of the parameter used if the parameter is a scalar

  • offset— Field offset in the JCo buffer used if a parameter is a scalar

  • decimals— Number of decimals a field contains; used with JCO.TYPE_BCD and JCO.TYPE_FLOAT

  • flags— Specifies whether the parameter is an import (JCO.IMPORT_PARAMETER), an export (JCO.EXPORT_PARAMETER), an optional (JCO.OPTIONAL_PARAMETER), an inactive (JCO.INACTIVE_PARAMETER), or a table (insert int value 0) parameter

  • structure_metadata— Takes an object, but for structures and tables, populate with the name of the underlying structure in SAP

When creating data for the structure, we do not need the flags or the table metadata. So, the addInfo() method has an overloaded method without these parameters added.

The final step is storing the JCO.MetaData objects in the repository. The repository object has two methods for this:

  • addFunctionInterfaceToCache()— Adds the function metadata to the repository

  • addStructureDefinitionToCache()— Adds the structure metadata to the repository

Now that we know what each function does, let's look at the code. Listing 32.3 is the static block within our wrapper class used to define our repository data.

Listing 32.3 Defining the JCo Repository
//for performance reasons, define a singleton for repository
  static {
    repository = new Repository("serverRepository");

    JCO.MetaData functionMetaData = new JCO.MetaData("RFC_CUSTOMER_GET");
    functionMetaData.addInfo("KUNNR",
           JCO.TYPE_CHAR,  10, 0, 0, JCO.IMPORT_PARAMETER , null);
    functionMetaData.addInfo("NAME1",   
           JCO.TYPE_CHAR,  35, 0, 0, JCO.IMPORT_PARAMETER, null);
    functionMetaData.addInfo("CUSTOMER_T", 
           JCO.TYPE_TABLE, 0, 0, 0, 0,  "BRFCKNA1");

    repository.addFunctionInterfaceToCache(functionMetaData);

    JCO.MetaData structureMetaData = new JCO.MetaData("BRFCKNA1");
    structureMetaData.addInfo("KUNNR", JCO.TYPE_CHAR, 10, 0, 0);
    structureMetaData.addInfo("ANRED", JCO.TYPE_CHAR,  15, 10, 0);
    structureMetaData.addInfo("NAME1",  JCO.TYPE_CHAR,  35, 25, 0);
    structureMetaData.addInfo("PFACH",  JCO.TYPE_CHAR,  10, 60, 0);
    structureMetaData.addInfo("STRAS",  JCO.TYPE_CHAR,  35, 70, 0);
    structureMetaData.addInfo("PSTLZ",  JCO.TYPE_CHAR,  10, 105, 0);
    structureMetaData.addInfo("ORT01",  JCO.TYPE_CHAR,  35, 115, 0);
    structureMetaData.addInfo("TELF1", JCO.TYPE_CHAR,  16, 150, 0);
    structureMetaData.addInfo("TELFX",  JCO.TYPE_CHAR,  31, 166, 0);

    repository.addStructureDefinitionToCache(structureMetaData);
  }

Notice how the offset length has to be specified in the structure metadata and not in the parameters. The offset is very important; if it's incorrect, the values will be populated incorrectly.

With the repository created and stored, we're ready to create the JCO.Server objects and ultimately receive messages from SAP.

Creating the JCO.Server Objects

The JCO.Server object must be extended to do anything useful with SAP. In our example, we create an inner class for extending the JCo server and create a constructor. The constructor takes a very specific set of arguments. The list of arguments in our example is as follows:

  • Gateway host— The SAP gateway server name or IP address

  • Gateway service number— The service number; usually sapgw00

  • Program ID— The program ID that must match the program ID specified in the RFC

  • Repository— The function Repository object

TIP

If you aren't familiar with these terms, contact your SAP basis administrator to help you set them up.



//Create a server to take requests from SAP
 static public class Server extends JCO.Server {

    public Server(String gwhost, String gwserv, 
           String progid, IRepository repository)
    {
      super(gwhost,gwserv,progid,repository);
    }
...
}

Now that we have the basic object taken care of, let's look at the handleRequest() method that must be overridden (it's empty { } by default). The handleRequest() method is called every time the SAP system contacts JCo. The lone argument passed is a JCO.Function object. At this point, the programmer is free to do whatever he wants. I suggest checking for each function your application supports, and then minimally writing a separate method for each function. To keep the example readable, our sample application only contains one function and populates a table if the customer is correctly matched. Let's examine the code shown in Listing 32.4.

Listing 32.4 Handling a Request from SAP
// The default method handles nothing. override for all functions that are needed
    protected void handleRequest(JCO.Function function)
{

  System.out.println("function " + function.getName() + " called from SAP");

  if (function.getName().equals("RFC_CUSTOMER_GET")) {
    JCO.Table tables = 
    function.getTableParameterList().getTable("CUSTOMER_T");
    System.out.println(
    function.getImportParameterList().getString("KUNNR"));
  if("1234567890".equals(function.getImportParameterList().getString("KUNNR"))){
    tables.appendRow();
    tables.setValue("1234567890", "KUNNR");
    tables.setValue("Michael Jordan", "NAME1");
    tables.setValue("91607", "PSTLZ");
    tables.setValue("USA", "STRAS");
    tables.setValue("CALIFORNIA", "ORT01");
    tables.setValue("818-555-5555", "TELF1");
   }
   else{
    throw new JCO.AbapException(
        "NO_RECORD_FOUND","No Current Record could be found!!!");
   }
  }
}//handleRequest

The code in Listing 32.4 could be separated into a separate method, but for this short example, there's no need to do so. Using the same methods we learned about on the client side, we first check the customer number KUNNR. If it matches the required value, we return a row of data to SAP. If it doesn't match, we throw an ABAP exception.

TIP

The first argument in the exception thrown has to match one of the exceptions in the RFC interface within SAP. If it doesn't, a short dump will occur in SAP. You can verify this by looking at transaction SE37, entering the program name, and clicking on the Exceptions tab.


SAP RFC Setup (SM59)

Without setting up the RFC destination within SAP, programs cannot call your Java application. There are some very specific instructions for setting up the RFC destination to work with your program. First, it must be a TCP/IP connection type. Second, the activation type must be Registration and the program ID must match the program ID given when the Java server was started. This is the link that registers your server to the SAP RFC. To set up an RFC, either you or a SAP basis person must log on to SAP and go to transaction SM59. Figure 32.3 shows the screen.

Figure 32.3. SAP RFC setup screen.

graphics/32fig03.gif

Running the Server Application

To run the server application, simply follow these steps:

  1. Copy the /server directory from the companion CD to your computer's hard drive.

  2. Set the correct path to your jco.jar or sapjco.jar in the startserver.bat file located in the /server directory.

  3. Make sure that java.exe is in your classpath.

  4. Start the JCO server by clicking on the startserver.bat file.

  5. Make sure that the RFC settings are correct as specified in the "SAP RFC Setup (SM59)" section earlier in this chapter.

  6. Go to transaction SE37 in SAP.

  7. Enter RFC_CUSTOMER_GET and press the F8 key to go to the Test Function Module screen.

  8. Fill in the RFC target sys: value with the correct RFC destination name from SM59.

  9. Populate the KUNNR value with 1234567890 to return address information or any other value to return a NO_RECORD_FOUND exception.

Figure 32.4 shows the testing screen in SE37.

Figure 32.4. Testing the Java server from inside SAP.

graphics/32fig04.gif

    [ Team LiB ] Previous Section Next Section