[ Team LiB ] |
Calling an SAP FunctionUntil this point, this chapter has dealt with establishing and discontinuing connections from SAP. The next step is to call the functions of and retrieve data from SAP. The first step is to create a JCO.Repository object. This object calls SAP and dynamically retrieves all the metadata for each function called. The other alternative, which is hard-coding the function's interface, is not a good idea because the interfaces can change as SAP systems are upgraded and support packs are installed. Calling one of three different constructors creates the repository. Each constructor has two different parameters. The first parameter is java.lang.String, which is used to name the repository. The second parameter is JCO.Client, a pool name, or an array of pool names. The following code uses a predefined pool name: JCO.Repository myRepository = new JCO.Repository("JCOrep", POOL_NAME); After the repository is created, it's used to get an IFunctionTemplate, which in turn returns a JCO.Function object with its getFunction() method. In the following example, the BAPI_MATERIAL_GETLIST BAPI is retrieved: JCO.Function myFunc = mrep.getFunctionTemplate( "BAPI_MATERIAL_GETLIST").getFunction(); The JCO.Function now contains information regarding its import, export, and table parameters as well as all the information necessary to complete a call to SAP. The actual call is performed within the JCO.Client object's execute() method: client.execute(myFunc); This BAPI will return all the BAPIs on the current system and pertinent information about them. To use the data, the programmer must know how to access the import, export, and table data. Java and SAP Type ConversionSAP and Java have very different concepts of data types. JCo has conversions for each of the SAP data types, a getString() method that returns the data type as a string, and a getValue() method that returns an object of the correct type. JCo tries to convert the type passed in to the associated ABAP type. If this isn't possible, a runtime exception is thrown. There are associated set and get methods for each Java type. The JavaDocs for JCO.Record contain a very helpful conversion table that explains how JCo converts each ABAP type into a Java type when a method is called. Table 32.4 lists the SAP types, the associated Java objects, and the access method used to retrieve the data.
When putting data into fields and tables for a call to SAP, the setValue() method is overloaded for use with all the JCo data types besides JCO.TYPE_DATE and JCO.TYPE_TIME. SAP expects the date in the format YYYYMMDD, but allows the dates 00000000 and 99999999 that break the java.lang.Date format. On the outbound, JCo changes 00000000 to null and 99999999 to 12/31/9999, but on the input, it's not correct to do this conversion. Instead, use the setValue() method to set a String object. Time conversions have a similar problem: Outbound values work as the Java date object, but the programmer must set inbound values by using a String value. JCo has introspection objects for conversion from SAP to Java. Every object that can access field-level data has a getType() method that gives the JCo type for the field. The following program snippet shows the conversion and gives an example of retrieving the data for every SAP data type. Of course, you can use the getString() method for every field if the program needs to display only the unformatted data as a string as on a Web site. JCO.Table matnrlist = jfun.getTableParameterList().getTable("MATNRLIST"); for(int rows =0; rows < matnrlist.getNumRows(); rows++,matnrlist.nextRow()){ for(int cols = 0; cols < matnrlist.getNumColumns(); cols++){ switch(matnrlist.getType(cols)){ case JCO.TYPE_INT1: case JCO.TYPE_INT2: case JCO.TYPE_INT: int jcoint = matnrlist.getInt(cols); break; case JCO.TYPE_CHAR: case JCO.TYPE_NUM: case JCO.TYPE_STRING: String jcostring = matnrlist.getString(cols); break; case JCO.TYPE_BCD: BigDecimal jcobcd = matnrlist.getBigDecimal(cols); break; case JCO.TYPE_DATE: java.util.Date jcodate = matnrlist.getDate(cols); break; case JCO.TYPE_TIME: java.util.Date jcotime = matnrlist.getTime(cols); break; case JCO.TYPE_FLOAT: double jcofloat = matnrlist.getDouble(cols); break; case JCO.TYPE_BYTE: case JCO.TYPE_XSTRING: byte[] jcobyte = matnrlist.getByteArray(cols); break; default: System.out.print("Incompatible Type"); } }//end of cols }//end of rows Setting and Getting DataJCo has helper classes for dealing with import, export, and table parameters. These classes encapsulate structures, tables, and fields. The JCO.Function object has associated methods to create the data structures needed to work with fields. The getImportParameterList(), getExportParameterList(), and getTableParameterList() methods return a JCO.ParameterList object that contains references to all the associated objects. The JCO.ParameterList object contains methods to get the individual object that needs to be populated. The methods take the name or the index of the parameter. Table 32.5 contains a list of these methods and what they return.
On simple scalar fields, it isn't necessary to convert to the JCO.Field object. The setValue() method has this capability. setValue() has two arguments: The first is the value and the second is the field name. myFunc.getImportParameterList().setValue("10","MAXROWS"); The MAXROWS field is now set to a value of 10. Remember that JCo has the capability to convert different types into the correct type for the field. If the data is incompatible with the type, JCo throws an exception. To set a structure value, first get the structure from the input parameter list: JCO.Structure matnrselection = jfun.getImportParameterList().getStructure("MATNRSELECTION"); This next step is to set the field with the setValue() method: matnrselection.setValue("I","SIGN"); For scalar fields and structures, it's very easy to retrieve data. The previous section discussed JCo types and the necessary access modifiers to retrieve them. The getString() method works for all types because JCo converts the values to java.lang.String. To access a single field, the call looks almost the same except that the getExportParameterList() function is used: int output = jfun.getExportParameterList().getInt("SOMEFIELD"); Working with structures is the same, except that we must get a JCO.Structure first and then get the value by passing in either the field name or the column index: JCO.Structure return = jfun.getExportParameterList().getStructure("RETURN"); String num = return.getString("NUMBER"); Although single fields and structures are important, most work with remote-enabled function modules is done through tables. Working with TablesSo far, this chapter has dealt with single fields and structures. Last, but certainly not least, we'll discuss working with tables. Although single fields and structures are specified as either import or export, table values are passed both in and out of SAP. Tables also contain multiple rows that must be traversed, created, and deleted. Before dealing with setting and getting the data, the first step is to retrieve the JCO.Table object from the function: JCO.Table matnrselection jfun.getTableParameterList("MATNRSELECTION"); The next step seems backward to most people. Instead of filling a row with data and then appending it to a table, JCo does it in the opposite order. First, create the row with the appendRow() method, and then set the value of the individual fields within the row: matnrselection.appendRow(); matnrselection.setValue("I","SIGN"); matnrselection.setValue("CP", "OPTION"); matnrselection.setValue("FERT-100","MATNR_LOW"); Internally in the JCO.Table object, when the appendRow() method is called, a row is created and the row pointer is set to the newly created row. For performance reasons, JCo allows multiple rows to be created at once with the appendRows(int rows) method that has the number of rows to be created as an argument. The row pointer is set to the first row that was created. JCo allows the manipulation of the row pointer through the following methods:
Now that the methods in JCO.Table are defined, let's manipulate them in a demo program. The BAPI_MATERIAL_GETLIST function will return a queried list of materials. This function uses MATNRSELECTION to send in the material query; we can use this just for an example and print out the result (see Listing 32.1). This is an excerpt from the JcoTableFun.java example that you can find in the /examples directory on the companion CD. In lines 1 and 2, the function is retrieved from SAP. Lines 3 and 4 get the table MATNRSELECTION and assign it to the variable matnrselection. Line 5 appends a row; line 6 sets the value of the MATNR_LOW field to the value "Row1". Line 7 creates three more rows. Line 8 sets the value of the second row. Line 9 moves to the next row. Line 10 sets the data in the third row. Line 11 sets the current row to the second row (remember that the row numbers start at 0). Line 12 deletes the current row. Line 13 goes to the last row. Line 14 sets the value of the last row to "LastRow". Line 15 inserts a row in the third position. Line 16 inserts the value "New 3rd Row" into the third row. Line 17 moves to the first row. Lines 18–22 print out the data in the MATNR_LOW field of the MATNRSELECTION table we just populated. Listing 32.1 Example of Manipulating Tables1 JCO.Function jfun = 2 mrep.getFunctionTemplate("BAPI_MATERIAL_GETLIST").getFunction(); 3 JCO.Table matnrselection = 4 jfun.getTableParameterList().getTable("MATNRSELECTION"); 5 matnrselection.appendRow(); 6 matnrselection.setValue("Row1","MATNR_LOW"); //set at first position 7 matnrselection.appendRows(3); 8 matnrselection.setValue("Row2","MATNR_LOW");//set at position 2 9 matnrselection.nextRow(); //move to the 3rd row 10 matnrselection.setValue("Row3","MATNR_LOW");//set at position 3 11 matnrselection.setRow(1); //go to the second row 12 matnrselection.deleteRow();//delete the current 2nd row 13 matnrselection.lastRow();//go to last row 14 matnrselection.setValue("LastRow","MATNR_LOW");//set at last row 15 matnrselection.insertRow(2);//insert a row in the 3rd position 16 matnrselection.setValue("New 3rd Row","MATNR_LOW");//set new 3rd row 17 matnrselection.firstRow();//move to first row 18 for(int rows = 0; rows < matnrselection.getNumRows(); rows++){ 19 System.out.print("Row = " + matnrselection.getRow() + " "); 20 System.out.println("Value = " + matnrselection.getString("MATNR_LOW")); 21 matnrselection.nextRow(); 22 } When the preceding sample from Listing 32.1 and JcoTableFun.java is compiled and run, the second row is deleted, the new third row is inserted, and there are exactly four rows: Row = 0 Value = Row1 Row = 1 Value = Row3 Row = 2 Value = New 3rd Row Row = 3 Value = LastRow The data within the rows is retrieved using the same functions discussed when using a structure. All the get methods covered in the data type conversion section also apply to a table row. Remember, the internal row pointer must point to the row where the data you need access to is stored. Transactions in SAPWhen building applications using multiple systems, the concept of transactions is very important. For instance, if an application needs to create a sales order within SAP and update a second database for a separate material management system, the complete logical unit of work would state that if the call to SAP succeeds but the database call fails, the application would roll back the SAP transaction to keep the data in sync. Most SAP BAPIs and RFCs do not implicitly support transactional type processing, specifically using two-phase commits. Some of the newer BAPIs support one-phase commits, but with one caveat: The programmer must write the code for the commit or rollback. Any function that supports this model must have special BAPIs called while holding the same connection to SAP. The two special-case BAPIs are
This would be a nice feature on all inbound SAP calls. Unfortunately, that isn't the case. In fact, without reading the documentation on the function, there's no way to tell whether a function needs a commit or even if the operation is supported. One frequently used BAPI that needs an external commit is BAPI_SALESORDER_CREATEFROMDAT2. Let's look at a sample call using this BAPI within a transaction as shown in Listing 32.2. The most important part of using the transaction BAPI is calling it with same connection without closing it. Listing 32.2 gives an example of using a transaction in with SAP. Listing 32.2 Transactional Processing in JCoJCO.Function bapi_sales_create = mrep.getFunctionTemplate("BAPI_SALESORDER_CREATEFROMDAT2").getFunction(); JCO.Function commit = mrep.getFunctionTemplate("BAPI_TRANSACTION_COMMIT").getFunction(); JCO.Function rollback = mrep.getFunctionTemplate("BAPI_TRANSACTION_ROLLBACK").getFunction(); try{ client.connect(); client.execute(bapi_sales_create); if(!"".equals(bapi_sales_create.getExportParameterList() .getString("SALESDOCUMENT"))) client.execute(commit); else client.execute(rollback); }catch(Exception e){ e.printStackTrace(); } JCO.releaseClient(client); ... This example is very simple and commits only when a valid document number is returned from the created sales order. The steps are the same even as the transaction processing needs grow:
NOTE This is a perfect place to use the EJB SessionSynchronization interface. The Commit function could be executed in the beforeCompletion() method, and could force a rollback if it fails. Filtering SAP DataCertain SAP functions can have hundreds of parameters. Storing all that data on the client side can have a serious impact on performance. JCo enables the programmer to filter out the import, export, and table parameters that aren't needed. This does not mean that they aren't transferred from SAP. The client receives all the data, but JCo just ignores the data that was set as inactive. The setActive() method is used to filter the parameters that aren't needed at runtime. The JCO.ParameterList object contains the setActive() method. Here's an example of filtering out a table that isn't needed:
bapi_sales_create.getTableParameterList().setActive(false, "ORDER_CFGS_INST");
JCo does not support the filtering of individual fields in a structure or table. |
[ Team LiB ] |