外部系統(Java)調用BAPI函數
在調用BAPI時,SAP為各編程環境(VB、C++、Java等)提供了RFC庫及SAP連接器(如Jco、Nco等)。這些類庫中的RFC API封閉了外部系統和SAP的連接細節
安裝JCo3
JCo有32位和64為之分,32位的JVM選擇32位的JCO, 64位的JVM選擇64位的JCO, 在windows環境,選擇相應的sapjco3.dll, Unix和Linux環境選擇合適的sapjco3.so
32位下載:http://pan.baidu.com/s/1jGr6jSa
64位下載:http://pan.baidu.com/s/1i3mO2rj
解壓后將sapjco3.dll拷貝到c:/windows/system32與C:\Program Files (x86)\Java\jdk1.7.0_51\bin下,將sapjco3.jar加入項目的classpath中。
測試安裝成功與否,很簡單,打開一個命令:
或者
java -cp C:/sapjco3.jar com.sap.conn.jco.rt.About
創建JCo3連接
JCo連接到SAP服務器有兩種方法,分別是直連和通過連接池進行連接。其差別在於,打開直連連接后可以一直保持連接;連接池則是在需要時才建立連接,連接暫不需要時,將被釋放回連接池,再分配給其他用戶使用。在網絡服務器應用程序里,一般采用連接池進行連接SAP服務器。
如果是老系統,可能要還注意遠程登錄用戶的類型:
直連
import java.io.File;
import java.io.FileOutputStream;
import java.util.Properties;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoDestinationManager;
import com.sap.conn.jco.JCoException;
import com.sap.conn.jco.ext.DestinationDataProvider;
public class ConnectNoPool {// 直連方式,非連接池
// 連接屬性配置文件名,名稱可以隨便取
static String ABAP_AS = "ABAP_AS_WITHOUT_POOL";
static {
Properties connectProperties = new Properties();
connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST,
"192.168.111.137");
connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
connectProperties
.setProperty(DestinationDataProvider.JCO_CLIENT, "800");
connectProperties.setProperty(DestinationDataProvider.JCO_USER,
"SAPECC");
// 注:密碼是區分大小寫的,要注意大小寫
connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD,
"sapecc60");
connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "en");
// 需要將屬性配置保存屬性文件,該文件的文件名為 ABAP_AS_WITHOUT_POOL.jcoDestination,
// JCoDestinationManager.getDestination()調用時會需要該連接配置文件,后綴名需要為jcoDestination
createDataFile(ABAP_AS, "jcoDestination", connectProperties);
}
// 基於上面設定的屬性生成連接配置文件
static void createDataFile(String name, String suffix, Properties properties) {
File cfg = new File(name + "." + suffix);
if (!cfg.exists()) {
try {
FileOutputStream fos = new FileOutputStream(cfg, false);
properties.store(fos, "for tests only !");
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void connectWithoutPool() throws JCoException {
// 到當前類所在目錄中搜索 ABAP_AS_WITHOUT_POOL.jcoDestination
// 屬性連接配置文件,並根據文件中的配置信息來創建連接
JCoDestination destination = JCoDestinationManager
.getDestination(ABAP_AS);// 只需指定文件名(不能帶擴展名jcoDestination名,會自動加上)
System.out.println("Attributes:");
// 調用destination屬性時就會發起連接,一直等待遠程響應
System.out.println(destination.getAttributes());
}
public static void main(String[] args) throws JCoException {
connectWithoutPool();
}
}
Attributes:
DEST: ABAP_AS_WITHOUT_POOL
OWN_HOST: jiangzhengjun
PARTNER_HOST: SAPECC6
SYSTNR: 00
SYSID: ECC
CLIENT: 800
USER: SAPECC
LANGUAGE: E
ISO_LANGUAGE: EN
OWN_CODEPAGE: 4102
OWN_CHARSET: UTF16
OWN_ENCODING: utf-16
OWN_BYTES_PER_CHAR: 2
PARTNER_CODEPAGE: 4103
PARTNER_CHARSET: UTF16
PARTNER_ENCODING: utf-16
PARNER_BYTES_PER_CHAR: 2
OWN_REL: 720
PARTNER_REL: 731
PARTNER_TYPE: 3
KERNEL_REL: 720
TRACE:
RFC_ROLE: C
OWN_TYPE: E
CPIC_CONVID: 00000000
連接池
程序運行結果與上面直接是一樣的
import java.io.File;
import java.io.FileOutputStream;
import java.util.Properties;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoDestinationManager;
import com.sap.conn.jco.JCoException;
import com.sap.conn.jco.ext.DestinationDataProvider;
public class ConnectPooled {// 連接池
static String ABAP_AS_POOLED = "ABAP_AS_WITH_POOL";
static {
Properties connectProperties = new Properties();
connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST,
"192.168.111.137");
connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
connectProperties
.setProperty(DestinationDataProvider.JCO_CLIENT, "800");
connectProperties.setProperty(DestinationDataProvider.JCO_USER,
"SAPECC");
// 注:密碼是區分大小寫的,要注意大小寫
connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD,
"sapecc60");
connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "en");
// *********連接池方式與直接不同的是設置了下面兩個連接屬性
// JCO_PEAK_LIMIT - 同時可創建的最大活動連接數,0表示無限制,默認為JCO_POOL_CAPACITY的值
// 如果小於JCO_POOL_CAPACITY的值,則自動設置為該值,在沒有設置JCO_POOL_CAPACITY的情況下為0
connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT,
"10");
// JCO_POOL_CAPACITY - 空閑連接數,如果為0,則沒有連接池效果,默認為1
connectProperties.setProperty(
DestinationDataProvider.JCO_POOL_CAPACITY, "3");
createDataFile(ABAP_AS_POOLED, "jcoDestination", connectProperties);
}
static void createDataFile(String name, String suffix, Properties properties) {
File cfg = new File(name + "." + suffix);
if (!cfg.exists()) {
try {
FileOutputStream fos = new FileOutputStream(cfg, false);
properties.store(fos, "for tests only !");
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void connectWithPooled() throws JCoException {
JCoDestination destination = JCoDestinationManager
.getDestination(ABAP_AS_POOLED);
System.out.println("Attributes:");
System.out.println(destination.getAttributes());
}
public static void main(String[] args) throws JCoException {
connectWithPooled();
}
}
DestinationDataProvider接口(不需連接屬性配置文件)
上面直接連接、連接池,兩種連接方法都需要先建立一個屬性配置文件,然后JCo再從建立好文件里讀取連接到SAP服務器所需要的連接屬性,這個方法很難在實際的環境中應用,存儲SAP連接屬性配置信息到一個文件里,是比較不安全的。然而,JCO為我們提供了另外一種連接的方法:DestinationDataProvider,通過它我們就可以將一個連接變量信息存放在內存里
import java.util.HashMap;
import java.util.Properties;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoDestinationManager;
importcom.sap.conn.jco.ext.DestinationDataEventListener;
import com.sap.conn.jco.ext.DestinationDataProvider;
import com.sap.conn.jco.ext.Environment;
public class CustomSAPDestinationDataProvider {
static class MyDestinationDataProvider implements DestinationDataProvider {
privateDestinationDataEventListenereL;
private HashMap<String, Properties>destinations;
private static MyDestinationDataProvider provider = new MyDestinationDataProvider();
private MyDestinationDataProvider() {// 單例模式
if (provider == null) {
destinations = new HashMap<String, Properties>();
}
}
public static MyDestinationDataProvider getInstance() {
return provider;
}
// 實現接口:獲取連接配置屬性
public Properties getDestinationProperties(String destinationName) {
if (destinations.containsKey(destinationName)) {
return destinations.get(destinationName);
} else {
throw new RuntimeException("Destination " + destinationName
+ " is not available");
}
}
public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {
this.eL = eventListener;
}
public boolean supportsEvents() {
return true;
}
/**
* Add new destination 添加連接配置屬性
*
* @param properties
* holds all the required data for a destination
**/
void addDestination(String destinationName, Properties properties) {
synchronized (destinations) {
destinations.put(destinationName, properties);
}
}
}
public static void main(String[] args) throws Exception {
// 獲取單例
MyDestinationDataProvider myProvider = MyDestinationDataProvider
.getInstance();
// Register the MyDestinationDataProvider 環境注冊
Environment.registerDestinationDataProvider(myProvider);
// TEST 01:直接測試
// ABAP_AS is the test destination name :ABAP_AS為目標連接屬性名(只是邏輯上的命名)
String destinationName = "ABAP_AS";
System.out.println("Test destination - " + destinationName);
Properties connectProperties = new Properties();
connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST,
"192.168.111.123");
connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
connectProperties
.setProperty(DestinationDataProvider.JCO_CLIENT, "800");
connectProperties.setProperty(DestinationDataProvider.JCO_USER,
"SAPECC");
connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD,
"sapecc60");
connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "en");
// Add a destination
myProvider.addDestination(destinationName, connectProperties);
// Get a destination with the name of "ABAP_AS"
JCoDestination DES_ABAP_AS = JCoDestinationManager
.getDestination(destinationName);
// Test the destination with the name of "ABAP_AS"
try {
DES_ABAP_AS.ping();
System.out.println("Destination - " + destinationName + " is ok");
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("Destination - " + destinationName
+ " is invalid");
}
// TEST 02:連接池測試
// Add another destination to test
// ABAP_AS2 is the test destination name
String destinationName2 = "ABAP_AS2";
System.out.println("Test destination - " + destinationName2);
Properties connectProperties2 = new Properties();
connectProperties2.setProperty(DestinationDataProvider.JCO_ASHOST,
"192.168.111.123");
connectProperties2.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
connectProperties2
.setProperty(DestinationDataProvider.JCO_CLIENT, "800");
connectProperties2.setProperty(DestinationDataProvider.JCO_USER,
"SAPECC");
connectProperties2.setProperty(DestinationDataProvider.JCO_PASSWD,
"sapecc60");
connectProperties2.setProperty(DestinationDataProvider.JCO_LANG, "en");
connectProperties2.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT,
"10");
connectProperties2.setProperty(
DestinationDataProvider.JCO_POOL_CAPACITY, "3");
// Add a destination
myProvider.addDestination(destinationName2, connectProperties2);
// Get a destination with the name of "ABAP_AS2"
JCoDestination DES_ABAP_AS2 = JCoDestinationManager
.getDestination(destinationName2);
// Test the destination with the name of "ABAP_AS2"
try {
DES_ABAP_AS2.ping();
System.out.println("Destination - " + destinationName2 + " is ok");
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("Destination - " + destinationName2
+ " is invalid");
}
}
}
訪問結構 (Structure)
public static void accessSAPStructure() throws JCoException {
JCoDestination destination = JCoDestinationManager
.getDestination(ABAP_AS);
JCoFunction function = destination.getRepository().getFunction(
"RFC_SYSTEM_INFO");//從對象倉庫中獲取 RFM 函數
if (function == null)
throw new RuntimeException(
"RFC_SYSTEM_INFO not found in SAP.");
try {
function.execute(destination);
} catch (AbapException e) {
System.out.println(e.toString());
return ;
}
JCoStructure exportStructure = function.getExportParameterList()
.getStructure("RFCSI_EXPORT");
System.out.println("System info for "
+ destination.getAttributes().getSystemID() + ":\n");
for (int i = 0; i < exportStructure.getMetaData().getFieldCount(); i++) {
System.out.println(exportStructure.getMetaData().getName(i) + ":\t"
+ exportStructure.getString(i));
}
System.out.println();
// JCo still supports the JCoFields, but direct access via getXX is more
// efficient as field iterator 也可以使用下面的方式來遍歷
System.out.println("The same using field iterator: \nSystem info for "
+ destination.getAttributes().getSystemID() + ":\n");
for (JCoField field : exportStructure) {
System.out.println(field.getName() + ":\t" + field.getString());
}
System.out.println();
//*********也可直接通過結構中的字段名或字段所在的索引位置來讀取某個字段的值
System.out.println("RFCPROTO:\t"+exportStructure.getString(0));
System.out.println("RFCPROTO:\t"+exportStructure.getString("RFCPROTO"));
}
public static void main(String[] args) throws JCoException {
accessSAPStructure();
}
訪問表 (Table)
public static void workWithTable() throws JCoException {
JCoDestination destination = JCoDestinationManager
.getDestination(ABAP_AS);
JCoFunction function = destination.getRepository().getFunction(
"BAPI_COMPANYCODE_GETLIST");//從對象倉庫中獲取 RFM 函數:獲取公司列表
if (function == null)
throw new RuntimeException(
"BAPI_COMPANYCODE_GETLIST not found in SAP.");
try {
function.execute(destination);
} catch (AbapException e) {
System.out.println(e.toString());
return ;
}
JCoStructure return Structure = function.getExportParameterList()
.getStructure("return ");
//判斷讀取是否成功
if (!(return Structure.getString("TYPE").equals("") || return Structure
.getString("TYPE").equals("S"))) {
throw new RuntimeException(return Structure.getString("MESSAGE"));
}
//獲取Table參數:COMPANYCODE_LIST
JCoTable codes = function.getTableParameterList().getTable(
"COMPANYCODE_LIST");
for (int i = 0; i < codes.getNumRows(); i++) {//遍歷Table
codes.setRow(i);//將行指針指向特定的索引行
System.out.println(codes.getString("COMP_CODE") + '\t'
+ codes.getString("COMP_NAME"));
}
// move the table cursor to first row
codes.firstRow();//從首行開始重新遍歷 codes.nextRow():如果有下一行,下移一行並返回True
for (int i = 0; i < codes.getNumRows(); i++, codes.nextRow()) {
//進一步獲取公司詳細信息
function = destination.getRepository().getFunction(
"BAPI_COMPANYCODE_GETDETAIL");
if (function == null)
throw new RuntimeException(
"BAPI_COMPANYCODE_GETDETAIL not found in SAP.");
function.getImportParameterList().setValue("COMPANYCODEID",
codes.getString("COMP_CODE"));
// We do not need the addresses, so set the corresponding parameter
// to inactive.
// Inactive parameters will be either not generated or at least
// converted. 不需要返回COMPANYCODE_ADDRESS參數(但服務器端應該還是組織了此數據,只是未經過網絡傳送?)
function.getExportParameterList().setActive("COMPANYCODE_ADDRESS",
false);
try {
function.execute(destination);
} catch (AbapException e) {
System.out.println(e.toString());
return ;
}
return Structure = function.getExportParameterList().getStructure(
"return ");
if (!(return Structure.getString("TYPE").equals("")
|| return Structure.getString("TYPE").equals("S") || return Structure
.getString("TYPE").equals("W"))) {
throw new RuntimeException(return Structure.getString("MESSAGE"));
}
JCoStructure detail = function.getExportParameterList()
.getStructure("COMPANYCODE_DETAIL");
System.out.println(detail.getString("COMP_CODE") + '\t'
+ detail.getString("COUNTRY") + '\t'
+ detail.getString("CITY"));
}// for
}
Java多線程調用有/無狀態RFM
有狀態調用:指多次調用某個程序(如多次調用某個RFC函數、調用某個函數組中的多個RFC函數、及BAPI函數——因為BAPI函數也是一種特殊的具有RFC功能的函數,它也有自己的函數組)時,在這一多次調用過程中,程序運行時的內存狀態(即全局變量的值)可以在每次調用后保留下來,供下一次繼續使用,而不是每次調用后,程序所在的內存狀態被清除。這種調用適用於那些使用到函數組中的全局變量的RFC函數的調用
無狀態調用:每次的調用都是獨立的一次調用(上一次調用與當前以及下一次調用之間不會共享任何全局變量),調用后不會保留內存狀態,這種調用適用於那些沒有使用到函數組中的全局變量的RFC函數調用
如果主調程序為Java時,需要通過遠程連接來調用RFC函數,此種情況下的有狀態調用的前提是:
l 多次調用RFC函數時,Java端要確保每次調用所使用的連接與上次是同一個(應該不需要是同一物理連接,只需要確保是同一遠程會話,從下面演示程序來看,用的是連接池,但同一任務執行時並未去特意使用同一物理連接去發送遠程調用,而只是要求是同一遠程會話)
l ABAP端需要在每次調用后,保留每一次被調用后函數組的內存狀態,直到最后一次調用完成止,這需要Java與ABAP配合來完成(Java在第一次調用時,調用JCoContext.begin、JCoContext.end這兩個方法,告訴SAP這一調用過程將是有狀態調用,然后SAP端會自動保留內存狀態)
如果主調程序是ABAP(即ABAP程序調用ABAP函數),此種情況下沒有特殊的要求,直接調用就即可,只要是在同一程序的同一運行會話其間(會話相當於Java中的同一線程吧),不管是多次調用同一個函數、還是調用同一函數組中的不同函數,則都會自動保留內存狀態,直到程序運行結束,這是系統自己完成的。實質上一個函數組就相當於一個類,函數組中不同的函數就相當於類中不同的方法、全局變量就相當於類中的屬性,所以只要是在同一程序的同一運行會話期間,調用的同一函數所在的函數組中的全局變量都是共享的,就好比調用一類的某個方法時,該方法設置了某個類的屬性,再去調用該類的其它方法時,該屬性值還是保留了以前其它方法修改后的狀態值。
該示例采用線程方式來演示,狀態調用只要保證同一線程中多次遠程方法調用采用的都是同一會話即可。當然更簡單的方法是不使用多線程,而直接在主線程中使用同一個物理遠程連接調用即可(但還是需要調用JCoContext.begin、JCoContext.end這兩個方法,告訴SAP端需要保留內存狀態,直接程序結束)
這里使用的函數是從標准程序COUNTER函數組拷貝而來,只不過系統中提供的不支持RFC調用,拷貝過來后修改成RFM:
import java.io.File;
import java.io.FileOutputStream;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Properties;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.sap.conn.jco.JCoContext;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoDestinationManager;
import com.sap.conn.jco.JCoException;
import com.sap.conn.jco.JCoFunction;
import com.sap.conn.jco.JCoFunctionTemplate;
import com.sap.conn.jco.ext.DestinationDataProvider;
import com.sap.conn.jco.ext.Environment;
import com.sap.conn.jco.ext.JCoSessionReference;
import com.sap.conn.jco.ext.SessionException;
import com.sap.conn.jco.ext.SessionReferenceProvider;
/**
* MultiThreadedExample is rather complex. It demonstrates演示 how to use the
* SessionReferenceProvider會話提供者 defined in the package com.sap.conn.jco.ext.
*
* Before discussing討論 situations情況 requiring要求 SessionReferenceProvider, we
* provide a short description of how the JCo Runtime handles the stateful(有狀態)
* and stateless(無狀態) calls by default. By default all RFC calls
* 默認情況下所有JCoFunction.execute執行都是無狀態的 (JCoFunction.execute(JCoDestination)) are
* stateless. That means the ABAP context associated with the connection will be
* destroyed(意味着上下連接被銷毀). Some RFC modules save a particular state/data in the
* ABAP context's area(有些函數組下的多個RFM會共用一全局變量). In order to keep a JCo connection
* and use it for subsequent (stateful) calls(為了保持多個RFM在同一連接中順序調用), the
* JCoConext.begin(JCoDestination) API can be used. In the case of multithreaded
* applications some calls to a destination can be executed concurrently(同時,並發),
* so JCo Runtime(JCo運行時環境需) needs to associate a particular call or connection
* to an internal session. By default JCo Runtime associates each thread with a
* session of its(默認情況下每個線程都有它自己的會話) own, so that most applications that execute
* all stateful requests en bloc(整體) or at least in the same thread will run
* correctly.
*
* Applications that wish to execute calls belonging to a stateful sequence by
* employing(采用) different threads have to implement and register the
* SessionReferenceProvider. The main goal of(主要目標) the implementation is to
* determine to which session the calls executing in the current thread belong.
*
* This example defines MultiStepJob having several execution
* steps(該示例的任務有多個步驟). The test starts a certain number of threads (see
* runJobs). Each thread is designed to take a job, execute one step, and put
* the job back to the shared job list. There are two jobs as an example:
* StatelessMultiStepExample and StatefulMultiStepExample. Both invoke the same
* RFC modules, but StatefulMultiStepExample uses JCoContext.begin and
* JCoContext.end to specify the stateful calls.
*
* To be able to execute a stateful call sequence distributed over several
* steps, we register a custom implementation of SessionReferenceProvider called
* MySessionReferenceProvider. The idea behind MySessionReferenceProvider is
* simple: each thread holds the current session reference in its local storage.
* To achieve(實現) that WorkerThread.run sets this session reference before
* executing the next step and removes it after the step is finished.
*/
public class MultiThreadedExample {
private static BlockingQueue<MultiStepJob>queue = new LinkedBlockingQueue<MultiStepJob>();
private static JCoFunctionTemplate incrementCounterTemplate,
getCounterTemplate;
// 任務接口
interface MultiStepJob {
String getName();//任務名
boolean isFinished();//任務是否
public void runNextStep();//運行任務
public void cleanUp();//清除任務
}
// 無狀態遠程RFM的調用(增加計數與讀取計數RFM雖然在這里是在同一會話中調用的——不一定是在同一連接中,
// 但沒有調用JCoContext.begin方法讓ABAP保留每次被調用后的內存狀態:計數器全局變量 count的值)
static class StatelessMultiStepExample implements MultiStepJob {
static AtomicInteger JOB_COUNT = new AtomicInteger(0);
int jobID = JOB_COUNT.addAndGet(1);// 任務編號
int calls;// 需要調用多少次
JCoDestination destination;// 遠程目標
int executedCalls = 0;// 記錄調用次數,即任務步驟
Exception ex = null;// 記錄任務執行過程出現的異常
int remoteCounter;// 計數結果
StatelessMultiStepExample(JCoDestination destination, int calls/* 調用次數 */) {
this.calls = calls;
this.destination = destination;
}
public boolean isFinished() {
// 如果Z_INCREMENT_COUNTER已經調用了10次,或者調用過程中出現了異常時,表示任務已完成
return executedCalls == calls || ex != null;
}
public String getName() {// 任務名
return "無狀態調用 Job-" + jobID;
}
// 任務的某一步,究竟有多少步則外界來傳遞進來的calls變量來控制
public void runNextStep() {
try {
//注:在調用遠程RFC功能函數(如這里的incrementCounter、getCounter)之前,JCo框架會去調用
// SessionReferenceProvider的getCurrentSessionReference()方法,
// 取得當前任務所對應的遠程會話,確保同一任務是在同一遠程會話中執行的
JCoFunction incrementCounter = incrementCounterTemplate
.getFunction();// 增加計數:即RFM中的count全局變量加一
incrementCounter.execute(destination);
executedCalls++;// 調用了多少次
if (isFinished()) {// 任務完后(這里調用10次),才讀取計數器
JCoFunction getCounter = getCounterTemplate.getFunction();
getCounter.execute(destination);
remoteCounter = getCounter.getExportParameterList().getInt(
"GET_VALUE");// 讀取計數:即讀取RFM中的count全局變量
}
} catch (JCoException je) {
ex = je;
} catch (RuntimeException re) {
ex = re;
}
}
public void cleanUp() {// 任務結束后,清除任務
StringBuilder sb = new StringBuilder("任務 ").append(getName())
.append(" 結束:");
if (ex != null) {
sb.append("異常結束 ").append(ex.toString());
} else {
sb.append("成功執行完,計數器值 = ").append(remoteCounter);
}
System.out.println(sb.toString());
}
}
// 有狀態遠程RFM調用(增加計數與讀取計數RFM在同一遠程會話中執行,保留了內存狀態:計數器全局變量 count的值)
static class StatefulMultiStepExample extends StatelessMultiStepExample {
StatefulMultiStepExample(JCoDestination destination, int calls) {
super(destination, calls);
}
@Override
public String getName() {
return "有狀態調用 Job-" + jobID;
}
@Override
public void runNextStep() {
// 如果是任務的第一步,則需要讓ABAP端保留函數運行后的上下文(內存)狀態
if (executedCalls == 0) {
// begin()與end()之間表示多個RFM執行會在同一個連接中執行,並且這之間的多個RFM屬於同一個LUW,並且按照調用的順序來執行
// ****不管是否有無狀態RFM調用(加begin后無狀態調用至少還可以保證同一任務中多個函數調用的順序),都要確保同一任務
// ****(多個RFM所組成的遠程調用任務)在同一會話中執行,要做到這一點,在Java端需要保證不同線程(同一線程也是)
// ****在執行同一任務時,JCo連接與遠程會話都要是同一個
JCoContext.begin(destination);// 開啟狀態調用,會話在begin與end之間不會被重置與關閉,這樣
// SAP端用戶的上下文件就會被保持
}
super.runNextStep();
}
@Override
public void cleanUp() {
try {
JCoContext.end(destination);
} catch (JCoException je) {
ex = je;
}
super.cleanUp();
}
}
static class MySessionReference implements JCoSessionReference {// 遠程會話實現
static AtomicInteger atomicInt = new AtomicInteger(0);
// 遠程會話ID
private String id = "session-" + String.valueOf(atomicInt.addAndGet(1));;
public void contextFinished() {
}
public void contextStarted() {
}
public String getID() {
return id;
}
}
// 工作線程,用來執行前面定義的任務:StatelessMultiStepExample、StatefulMultiStepExample
static class WorkerThread extends Thread {
// 任務與遠程會話映射關系表:確保同一任務要在同一遠程會話中執行
static Hashtable<MultiStepJob, MySessionReference>sessions = new Hashtable<MultiStepJob, MySessionReference>();
// ThreadLocal:線程全局變量局部化,即將原本共享的屬性全局變量在每個線程中都拷貝一份,不會讓它們再在不同的線程中共享,
// 每個線程拿到的都是自己所獨享的,所以看似全局共享的屬性在多線程情況下,也不會出現多線程並發問題
// 當前線程所使用的遠程會話
static ThreadLocal<MySessionReference>localSessionReference = new ThreadLocal<MySessionReference>();
// 同步器:倒計時閉鎖;threadCount為倒計數值,直到該數為0時,await()才會結束繼續往下執行
// CountDownLatch同步器的作用就是讓所有線程都准備好以后,真正同時開始執行,這樣不會因為先創建的
// 的線程就會先執行,可以真正模擬多線程同時執行的情況,這樣在研究多線程在訪問同一臨界資源時,容易發現線程並發問題
private CountDownLatch startSignal;// 開始閥:所以線程都已啟動並就緒時,所有線程不再阻塞
private CountDownLatch doneSignal;// 結束閥:所以線程結束后,主線程才結束
WorkerThread(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
// 工作線程
public void run() {
startSignal.countDown();
try {
startSignal.await();// 所有線程都已經運行到這里后,才開始一起同時向下執行,否則一直阻塞
// 某一時間段內(即一次循環)只執行某個任務的一個步驟
for (;;) {// 直到任務隊列中沒有任務時退出
// 出隊,工作線程從任務隊列中取任務:如果等10秒都未取到,則返回NULL
MultiStepJob job = queue.poll(10, TimeUnit.SECONDS);
// stop if nothing to do
if (job == null) {// 如果任務隊列中沒有任務后,工作線程將退出
return ;
}
// 取任務所對應的遠程會話,確保每個任務使用同一遠程會話
MySessionReference sesRef = sessions.get(job);
if (sesRef == null) {// 如果是第一次,則新創建一個遠程會話,再將任務與該會話進行綁定
sesRef = new MySessionReference();
sessions.put(job, sesRef);
}
// 存儲當前線程所使用的遠程會話。該值的讀取是在調用遠程RFM前,由JCo框架的
// SessionReferenceProvider的getCurrentSessionReference()方法來讀取
// ****不管是否有無狀態RFM調用,最好都要確保同一任務(多個RFM所組成的遠程調用任務)在同一會話中執行
// ****,要做到這一點,在Java端需要保證不同線程(同一線程也是)在執行同一任務時,遠程會話要是同一個
// 注:同一任務需要設置為同一遠程會話,不同任務不能設置為相同的遠程會話,否則計數器會在多個任務中共用
localSessionReference.set(sesRef);
System.out.println("任務 " + job.getName() + " 開始執行.");
try {
// 執行任務
job.runNextStep();
} catch (Throwable th) {
th.printStackTrace();
}
// 如果任務完成(調用遠程RFM計數器函數10次)
if (job.isFinished()) {
System.out.println("任務 " + job.getName() + " 執行完成.");
// 如果任務執行完了,則從映射表是刪除任務與遠程會話映射記錄
sessions.remove(job);
job.cleanUp();// 任務的所有步驟執行完后,輸出任務結果
} else {
System.out.println("任務 " + job.getName()
+ " 未完成,重新放入任務隊列,等待下次繼續執行.");
// 如果發現任務還沒有執行完,則重新放入任務隊列中,等待下一次繼續執行。從這里可以看出
// 計數器的增加與讀取可能是由不同的工作線程來完成的,但要確保同一任務是在同一遠程會話中調用的
queue.add(job);
}
// 當某個任務某一步執行完后,清除當前線程所存儲的遠程會話。注:這里的工作線程某一時間段內(即一次循環內)只能執行一個任務
localSessionReference.set(null);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
doneSignal.countDown();
}
}
}
// 遠程會話提供者:負責拿到當前任務的遠程會話
static class MySessionReferenceProvider implements SessionReferenceProvider {
public JCoSessionReference getCurrentSessionReference(String scopeType) {
// 從當前線程中讀取相應的遠程會話,這樣確保了同一任務中多個RFM的調用是在同一遠程會話連接中執行的
MySessionReference sesRef = WorkerThread.localSessionReference
.get();
if (sesRef != null) {
return sesRef;
}
throw new RuntimeException("Unknown thread:"
+ Thread.currentThread().getId());
}
// 遠程會話是否活着,JCo框架調用此來決定此連接是否銷毀?
public boolean isSessionAlive(String sessionId) {
Collection<MySessionReference> availableSessions = WorkerThread.sessions
.values();
for (MySessionReference ref : availableSessions) {
if (ref.getID().equals(sessionId)) {
return true;
}
}
return false;
}
public void jcoServerSessionContinued(String sessionID)
throws SessionException {
}
public void jcoServerSessionFinished(String sessionID) {
}
public void jcoServerSessionPassivated(String sessionID)
throws SessionException {
}
public JCoSessionReference jcoServerSessionStarted()
throws SessionException {
return null;
}
}
// 創建任務與工作線程並拉起
static void runJobs(JCoDestination destination, int jobCount,
int threadCount) {
System.out.println(">>>啟動");
for (int i = 0; i < jobCount; i++) {// 5*2=10 個任務(一半是狀態調用,一半是無狀態調用)
// 添加RFM無狀態調用任務
queue.add(new StatelessMultiStepExample(destination, 10/*
* 每個任務需要調用10次
* Z_INCREMENT_COUNTER
* 后,任務才算完成
*/));
// 添加RFM有狀態調用任務
queue.add(new StatefulMultiStepExample(destination, 10));
}
CountDownLatch startSignal = new CountDownLatch(threadCount);
CountDownLatch doneSignal = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
// 2 個工作線程,共同來完成10 個任務
new WorkerThread(startSignal, doneSignal).start();// 創建並啟動工作線程
}
System.out.println(">>>等待執行任務... ");
try {
doneSignal.await();// 主線程等待所有工作任務線程完成后,才結束
} catch (InterruptedException ie) {
ie.printStackTrace();
}
System.out.println(">>>完成");
}
public static void main(String[] argv) {
// JCo.setTrace(5, ".");
Environment
.registerSessionReferenceProvider(new MySessionReferenceProvider());
try {
JCoDestination destination = JCoDestinationManager
.getDestination(ABAP_AS);
// 遠程函數模板
incrementCounterTemplate = destination.getRepository()
.getFunctionTemplate("Z_INCREMENT_COUNTER");// 增加計數:即RFM中的count全局變量加一
getCounterTemplate = destination.getRepository()
.getFunctionTemplate("Z_GET_COUNTER");// 讀取計數:RFM中的count全局變量
if (incrementCounterTemplate == null || getCounterTemplate == null) {
throw new RuntimeException(
"This example cannot run without Z_INCREMENT_COUNTER and Z_GET_COUNTER functions");
}
// 2 個工作線程,5*2=10 個任務(一半是狀態調用,一半是無狀態調用)
runJobs(destination, 5, 2);
} catch (JCoException je) {
je.printStackTrace();
}
}
// 連接屬性配置文件名,名稱可以隨便取
static String ABAP_AS = "ABAP_AS_WITH_POOL";
static {
Properties connectProperties = new Properties();
connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST,
"192.168.111.137");
connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
connectProperties
.setProperty(DestinationDataProvider.JCO_CLIENT, "800");
connectProperties.setProperty(DestinationDataProvider.JCO_USER,
"SAPECC");
// 注:密碼是區分大小寫的,要注意大小寫
connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD,
"sapecc60");
connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "en");
// ****使用連接池的方式
connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT,
"10");
connectProperties.setProperty(
DestinationDataProvider.JCO_POOL_CAPACITY, "3");
// 需要將屬性配置保存屬性文件,該文件的文件名為 ABAP_AS_WITHOUT_POOL.jcoDestination,
// JCoDestinationManager.getDestination()調用時會需要該連接配置文件,后綴名需要為jcoDestination
createDataFile(ABAP_AS, "jcoDestination", connectProperties);
}
// 基於上面設定的屬性生成連接屬性配置文件
static void createDataFile(String name, String suffix, Properties properties) {
File cfg = new File(name + "." + suffix);
if (!cfg.exists()) {
try {
FileOutputStream fos = new FileOutputStream(cfg, false);
properties.store(fos, "for tests only !");
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
>>>啟動
>>>等待執行任務...
任務無狀態調用 Job-1 開始執行.
任務有狀態調用 Job-2 開始執行.
任務無狀態調用 Job-1 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-3 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-4 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務有狀態調用 Job-2 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務無狀態調用 Job-5 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-7 開始執行.
任務無狀態調用 Job-7 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務有狀態調用 Job-8 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務有狀態調用 Job-6 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務無狀態調用 Job-9 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務有狀態調用 Job-10 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-1 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-4 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-2 開始執行.
任務有狀態調用 Job-2 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務無狀態調用 Job-5 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-7 開始執行.
任務無狀態調用 Job-3 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務無狀態調用 Job-7 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務有狀態調用 Job-8 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務有狀態調用 Job-6 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務有狀態調用 Job-10 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務無狀態調用 Job-9 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-4 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-2 開始執行.
任務無狀態調用 Job-1 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務有狀態調用 Job-2 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-5 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-7 開始執行.
任務無狀態調用 Job-3 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務有狀態調用 Job-8 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務無狀態調用 Job-7 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務有狀態調用 Job-10 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務有狀態調用 Job-6 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-4 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務無狀態調用 Job-9 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-2 開始執行.
任務無狀態調用 Job-1 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務有狀態調用 Job-2 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-5 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務無狀態調用 Job-3 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-7 開始執行.
任務有狀態調用 Job-8 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務有狀態調用 Job-10 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務無狀態調用 Job-7 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-6 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務有狀態調用 Job-4 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務無狀態調用 Job-1 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-2 開始執行.
任務無狀態調用 Job-9 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務有狀態調用 Job-2 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-5 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務無狀態調用 Job-3 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務有狀態調用 Job-10 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-7 開始執行.
任務有狀態調用 Job-8 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務有狀態調用 Job-6 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務無狀態調用 Job-7 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務有狀態調用 Job-4 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務無狀態調用 Job-1 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-2 開始執行.
任務無狀態調用 Job-9 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務有狀態調用 Job-2 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-5 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務無狀態調用 Job-3 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務有狀態調用 Job-10 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務有狀態調用 Job-8 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-7 開始執行.
任務有狀態調用 Job-6 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-4 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務無狀態調用 Job-7 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務無狀態調用 Job-1 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-2 開始執行.
任務無狀態調用 Job-9 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務有狀態調用 Job-2 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-5 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務無狀態調用 Job-3 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務有狀態調用 Job-10 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務有狀態調用 Job-8 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-6 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-7 開始執行.
任務有狀態調用 Job-4 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務無狀態調用 Job-1 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務無狀態調用 Job-7 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-2 開始執行.
任務有狀態調用 Job-2 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務無狀態調用 Job-9 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-5 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務無狀態調用 Job-3 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務有狀態調用 Job-10 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務有狀態調用 Job-8 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-6 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務有狀態調用 Job-4 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-7 開始執行.
任務無狀態調用 Job-1 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-2 開始執行.
任務無狀態調用 Job-7 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務有狀態調用 Job-2 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-5 開始執行.
任務無狀態調用 Job-9 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-3 開始執行.
任務無狀態調用 Job-5 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務有狀態調用 Job-10 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-8 開始執行.
任務無狀態調用 Job-3 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-6 開始執行.
任務有狀態調用 Job-8 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-4 開始執行.
任務有狀態調用 Job-6 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-1 開始執行.
任務有狀態調用 Job-4 執行完成.
任務有狀態調用 Job-4 結束:成功執行完,計數器值 = 10
任務無狀態調用 Job-7 開始執行.
任務無狀態調用 Job-1 執行完成.
任務無狀態調用 Job-1 結束:成功執行完,計數器值 = 0
任務有狀態調用 Job-2 開始執行.
任務無狀態調用 Job-7 未完成,重新放入任務隊列,等待下次繼續執行.
任務無狀態調用 Job-9 開始執行.
任務有狀態調用 Job-2 執行完成.
任務有狀態調用 Job-2 結束:成功執行完,計數器值 = 10
任務無狀態調用 Job-5 開始執行.
任務無狀態調用 Job-9 未完成,重新放入任務隊列,等待下次繼續執行.
任務有狀態調用 Job-10 開始執行.
任務無狀態調用 Job-5 執行完成.
任務無狀態調用 Job-5 結束:成功執行完,計數器值 = 0
任務無狀態調用 Job-3 開始執行.
任務有狀態調用 Job-10 執行完成.
任務無狀態調用 Job-3 執行完成.
任務無狀態調用 Job-3 結束:成功執行完,計數器值 = 0
任務有狀態調用 Job-8 開始執行.
任務有狀態調用 Job-8 執行完成.
任務有狀態調用 Job-8 結束:成功執行完,計數器值 = 10
任務有狀態調用 Job-6 開始執行.
任務有狀態調用 Job-6 執行完成.
任務有狀態調用 Job-10 結束:成功執行完,計數器值 = 10
任務無狀態調用 Job-7 開始執行.
任務有狀態調用 Job-6 結束:成功執行完,計數器值 = 10
任務無狀態調用 Job-9 開始執行.
任務無狀態調用 Job-9 執行完成.
任務無狀態調用 Job-9 結束:成功執行完,計數器值 = 0
任務無狀態調用 Job-7 執行完成.
任務無狀態調用 Job-7 結束:成功執行完,計數器值 = 0
>>>完成
ABAP類型與JCo類型映射關系表
ABAP訪問Java服務
ABAP(作為Clint端),調用JAVA(作為服務器端)。
SAP通過JCO反向調用JAVA的RFC服務其實也是相對簡單的,只是在JAVA端需要使用JCO創建一個RFC服務,然后在SAP端注冊這個服務程序。
首先,JCo服務器程序需在網關中進行注冊,在SM59中,定義一個連接類型為T的遠程目標
RFC目標系統:是ABAP RFC調用Java時,需指定的目標系統名。
Program ID:是JAVA程序中使用的
Gateway Host與Gateway service值來自以下界面(Tcode:SMGW):
這里的Geteway Host就是下圖的應用程序服務器地址。 TCP服務sapgw是固定的,后面的00就是下圖的系統編號。
所有配置好且Java服務器代碼跑起來后,點擊“Connection Test”按鈕,如不出現紅色文本,則表示鏈接成功(注:此時需要ServerDataProvider.JCO_PROGID設置的Program ID要與SM59中設置的相同,否則測試不成功。另要注意的是:即使Java服務器設置的Program ID亂設置,Java服務端還是能啟起來,但ABAP測試連接時會不成功,也就代表ABAP不能調用Java):
此時可以通過SMGW來觀測連接:
連接異常registrationnot allowed
Java服務啟動時,如出現以下異常,則需在SAP中修改網關參數:
com.sap.conn.jco.JCoException: (113) JCO_ERROR_REGISTRATION_DENIED: CPIC-CALL: SAP_CMACCPTP3 on convId:
LOCATION SAP-Gateway on host LRP-ERP / sapgw00
ERROR registration of tp JCOTEST from host JIANGZHENGJUN not allowed
TIME Wed Apr 16 21:25:39 2014
RELEASE 720
COMPONENT SAP-Gateway
VERSION 2
RC 720
MODULE gwxxrd.c
LINE 3612
COUNTER 275
請參考JCo3包中的示例
無狀態訪問
ABAP客戶端代碼
DATA: resptext LIKE sy-lisel,
echotext LIKE sy-lisel.
DATA: rfc_mess(128).
"注:這里調用的不是本地SAP系統中的STFC_CONNECTION,而是遠程Java系統中所對應的
"函數,此函數的功能由Java服務器端實現,但此函數一定要在本地定義簽名(無需實現功能),
"否則需要在Java服務器端動態的通過JCoCustomRepository來創建函數對象庫
"CALL FUNCTION 'STFC_CONNECTION'"由於STFC_CONNECTION已存在於SAP本地中,所以無需在Java服務端創建函數對象庫,與此函數遠程調用的配套示例代碼為 Java 端的simpleServer() 方法
CALL FUNCTION 'ZSTFC_CONNECTION'"ZSTFC_CONNECTION在ABAP本地不存,因此還需要在Java服務端創建此函數對象庫,與此函數遠程調用的配套示例代碼為 Java 端的staticRepository() 方法
DESTINATION 'JCOTEST'"SM59中配置的RFC目標系統
EXPORTING
requtext = 'ABAP端發送的消息,並需要回傳'"需發送的文本
IMPORTING
resptext = resptext"服務器響應文本
echotext = echotext"回顯文本
EXCEPTIONS
system_failure = 1 MESSAGE rfc_mess
communication_failure = 2 MESSAGE rfc_mess.
IF sy-subrc NE 0.
WRITE: / 'Call function error SY-SUBRC = ', sy-subrc.
WRITE: / rfc_mess.
ELSE.
WRITE:/ resptext,echotext.
ENDIF.
*****************************下面ABAP程序是用來配合測試Java端的transactionRFCServer() 方法的
DATA: resptext LIKE sy-lisel,
echotext LIKE sy-lisel.
DATA: rfc_mess(128).
CALL FUNCTION 'STFC_CONNECTION'
IN BACKGROUND TASK
DESTINATION 'JCOTEST'
EXPORTING
requtext = 'ABAP端發送的消息'
EXCEPTIONS
system_failure = 1 message rfc_mess
communication_failure = 2 message rfc_mess.
DATA: tid TYPE arfctid.
CALL FUNCTION 'ID_OF_BACKGROUNDTASK'
IMPORTING
tid = tid."此事務ID與Java端打印出來的是一樣的
WRITE:/ 'TID = ',tid.
COMMIT WORK AND WAIT .
DATA: errortab TYPE STANDARD TABLE OF arfcerrors WITH HEADER LINE.
DO.
"獲取事務狀態,如果事務運行完,則sy-subrc為0
CALL FUNCTION 'STATUS_OF_BACKGROUNDTASK'
EXPORTING
tid = tid
TABLES
errortab = errortab[]
EXCEPTIONS
communication = 1"連接不可用:過會重試
recorded = 2"異步RFC調用處於計划中
rollback = 3."目標系統已經回滾
WRITE:/ sy-subrc.
IF sy-subrc = 0.
EXIT.
ELSE.
LOOP AT errortab.
WRITE: / errortab.
ENDLOOP.
"如果事務未完成,則等一秒后再判斷其狀態,直到事務完成
WAIT UP TO 1 SECONDS.
ENDIF.
ENDDO.
Java服務端代碼
import java.io.File;
import java.io.FileOutputStream;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import com.sap.conn.jco.JCo;
import com.sap.conn.jco.JCoCustomRepository;
import com.sap.conn.jco.JCoDestinationManager;
import com.sap.conn.jco.JCoException;
import com.sap.conn.jco.JCoFunction;
import com.sap.conn.jco.JCoFunctionTemplate;
import com.sap.conn.jco.JCoListMetaData;
import com.sap.conn.jco.JCoMetaData;
import com.sap.conn.jco.ext.DestinationDataProvider;
import com.sap.conn.jco.ext.ServerDataProvider;
import com.sap.conn.jco.server.DefaultServerHandlerFactory;
import com.sap.conn.jco.server.JCoServer;
import com.sap.conn.jco.server.JCoServerContext;
import com.sap.conn.jco.server.JCoServerContextInfo;
import com.sap.conn.jco.server.JCoServerErrorListener;
import com.sap.conn.jco.server.JCoServerExceptionListener;
import com.sap.conn.jco.server.JCoServerFactory;
import com.sap.conn.jco.server.JCoServerFunctionHandler;
import com.sap.conn.jco.server.JCoServerState;
import com.sap.conn.jco.server.JCoServerStateChangedListener;
import com.sap.conn.jco.server.JCoServerTIDHandler;
public class StepByStepServer {
static String SERVER_NAME1 = "SERVER";
static String DESTINATION_NAME1 = "ABAP_AS_WITHOUT_POOL";
static String DESTINATION_NAME2 = "ABAP_AS_WITH_POOL";
static MyTIDHandler myTIDHandler = null;
static {
JCo.setTrace(4, null);// 打開調試
Properties connectProperties = new Properties();
// ******直連
connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST,
"192.168.111.137");
connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
connectProperties
.setProperty(DestinationDataProvider.JCO_CLIENT, "800");
connectProperties.setProperty(DestinationDataProvider.JCO_USER,
"SAPECC");
connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD,
"sapecc60");
connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "en");
createDataFile(DESTINATION_NAME1, "jcoDestination", connectProperties);
// ******連接池
connectProperties.setProperty(
DestinationDataProvider.JCO_POOL_CAPACITY, "3");
connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT,
"10");
createDataFile(DESTINATION_NAME2, "jcoDestination", connectProperties);
// ******JCo sever
Properties servertProperties = new Properties();
servertProperties.setProperty(ServerDataProvider.JCO_GWHOST,
"192.168.111.137");
// TCP服務sapgw是固定的,后面的00就是SAP實例系統編號,也可直接是端口號(端口號可以在
// etc/server文件中找sapgw00所對應的端口號)
servertProperties.setProperty(ServerDataProvider.JCO_GWSERV, "sapgw00");
// 這里的程序ID來自於SM59中設置的Program ID,必須相同
servertProperties
.setProperty(ServerDataProvider.JCO_PROGID, "JCO_TEST");
servertProperties.setProperty(ServerDataProvider.JCO_REP_DEST,
DESTINATION_NAME2);
servertProperties.setProperty(ServerDataProvider.JCO_CONNECTION_COUNT,
"2");
createDataFile(SERVER_NAME1, "jcoServer", servertProperties);
}
static void createDataFile(String name, String suffix, Properties properties) {
File cfg = new File(name + "." + suffix);
if (!cfg.exists()) {
try {
FileOutputStream fos = new FileOutputStream(cfg, false);
properties.store(fos, "for tests only !");
fos.close();
} catch (Exception e) {
throw new RuntimeException(
"Unable to create the destination file "
+ cfg.getName(), e);
}
}
}
// 處理來自ABAP端的調用請求,實現注冊過的虛擬函數真正功能
static class StfcConnectionHandler implements JCoServerFunctionHandler {
public void handleRequest(JCoServerContext serverCtx,
JCoFunction function) {// 處理遠程調用請求
System.out
.println("----------------------------------------------------------------");
System.out.println("call : " + function.getName());// ABAP調用的是哪個函數
System.out.println("ConnectionId : "
+ serverCtx.getConnectionID());
System.out.println("SessionId : "
+ serverCtx.getSessionID());
System.out.println("TID : " + serverCtx.getTID());
System.out.println("repository name : "
+ serverCtx.getRepository().getName());
System.out.println("is in transaction : "
+ serverCtx.isInTransaction());
System.out.println("is stateful : "
+ serverCtx.isStatefulSession());
System.out
.println("----------------------------------------------------------------");
System.out.println("gwhost: "
+ serverCtx.getServer().getGatewayHost());
System.out.println("gwserv: "
+ serverCtx.getServer().getGatewayService());
System.out.println("progid: "
+ serverCtx.getServer().getProgramID());
System.out
.println("----------------------------------------------------------------");
System.out.println("attributes : ");
System.out.println(serverCtx.getConnectionAttributes().toString());
System.out
.println("----------------------------------------------------------------");
System.out.println("CPIC conversation ID: "
+ serverCtx.getConnectionAttributes()
.getCPICConversationID());
System.out
.println("----------------------------------------------------------------");
System.out.println("req text: "
+ function.getImportParameterList().getString("REQUTEXT"));
// function.getExportParameterList().setValue("ECHOTEXT",
// function.getImportParameterList().getString("REQUTEXT"));
// function.getExportParameterList().setValue("RESPTEXT",
// "Java服務端響應的消息");
}
}
static class MyThrowableListener implements JCoServerErrorListener,
JCoServerExceptionListener {// 服務異常監聽器
public void serverErrorOccurred(JCoServer jcoServer,
String connectionId, JCoServerContextInfo serverCtx, Error error) {
System.out.println(">>> Error occured on "
+ jcoServer.getProgramID() + " connection " + connectionId);
error.printStackTrace();
}
public void serverExceptionOccurred(JCoServer jcoServer,
String connectionId, JCoServerContextInfo serverCtx,
Exception error) {
System.out.println(">>> Error occured on "
+ jcoServer.getProgramID() + " connection " + connectionId);
error.printStackTrace();
}
}
static class MyStateChangedListener implements
JCoServerStateChangedListener {// 服務狀態改變監聽器
public void serverStateChangeOccurred(JCoServer server,
JCoServerState oldState, JCoServerState newState) {
// Defined states are: STARTED啟動, DEAD死, ALIVE活, STOPPED停止;
// see JCoServerState class for details.
// Details for connections managed by a server instance
// are available via JCoServerMonitor.
System.out.println("Server state changed from "
+ oldState.toString() + " to " + newState.toString()
+ " on server with program id " + server.getProgramID());
}
}
// 簡單調用:提供的函數需要在ABAP簽名
static void simpleServer() {
JCoServer server;
try {
server = JCoServerFactory.getServer(SERVER_NAME1);
} catch (JCoException ex) {
throw new RuntimeException("Unable to create the server "
+ SERVER_NAME1 + " because of " + ex.getMessage(), ex);
}
JCoServerFunctionHandler stfcConnectionHandler = new StfcConnectionHandler();
DefaultServerHandlerFactory.FunctionHandlerFactory factory = new DefaultServerHandlerFactory.FunctionHandlerFactory();
// 向SAP服務器注冊可提供的函數有哪些,告訴SAP系統,Java這邊可以提供STFC_CONNECTION這樣一個遠程函數,但具體的功能由StfcConnectionHandler來完成
// 注:因該可以注冊多個這樣的虛擬函數,都由 JCoServerFunctionHandler
// 的實現類來處理,在處理時可以由JCoFunction參數來判斷具體是哪個函數,走不同的處理邏輯
// 注:STFC_CONNECTION需要先在SAP端定義(但不需要在ABAP中實現),否則需要在Java端動態創建函數對象倉庫(請參考staticRepository方法)
factory.registerHandler("STFC_CONNECTION", stfcConnectionHandler);
server.setCallHandlerFactory(factory);
// ********* 添加一些連接狀態監聽處理器,便於在連接過程中定位問題(可以不用設置)
MyThrowableListener eListener = new MyThrowableListener();// 異常監聽,在連接過程中出問題時會被監聽到
server.addServerErrorListener(eListener);
server.addServerExceptionListener(eListener);
MyStateChangedListener slistener = new MyStateChangedListener();// 連接狀態監聽
server.addServerStateChangedListener(slistener);
server.start();
}
// 在Java服務端定義遠程函數(不需要在ABAP端進行函數的簽名定義)
static void staticRepository() {
JCoListMetaData impList = JCo.createListMetaData("IMPORT");
impList.add("REQUTEXT", JCoMetaData.TYPE_CHAR, 100, 50, 0, null, null,
JCoListMetaData.IMPORT_PARAMETER, null, null);
impList.lock();// 鎖住,不允許再修改
JCoListMetaData expList = JCo.createListMetaData("EXPORT");
expList.add("RESPTEXT", JCoMetaData.TYPE_CHAR, 100, 50, 0, null, null,
JCoListMetaData.EXPORT_PARAMETER, null, null);
expList.add("ECHOTEXT", JCoMetaData.TYPE_CHAR, 100, 50, 0, null, null,
JCoListMetaData.EXPORT_PARAMETER, null, null);
expList.lock();
// 注:ZSTFC_CONNECTION函數不必要在ABAP端時行定義了(只定義簽名,不需要實現),因為在這里(Java)
// 進行了動態的函數對象創建的創建與注冊,這與上面simpleServer方法示例是不一樣的
JCoFunctionTemplate fT = JCo.createFunctionTemplate("ZSTFC_CONNECTION",
impList, expList, null, null, null);
JCoCustomRepository cR = JCo
.createCustomRepository("MyCustomRepository");
cR.addFunctionTemplateToCache(fT);
JCoServer server;
try {
server = JCoServerFactory.getServer(SERVER_NAME1);
} catch (JCoException ex) {
throw new RuntimeException("Unable to create the server "
+ SERVER_NAME1 + " because of " + ex.getMessage(), ex);
}
String repDest = server.getRepositoryDestination();
if (repDest != null) {
try {
cR.setDestination(JCoDestinationManager.getDestination(repDest));
} catch (JCoException e) {
e.printStackTrace();
System.out
.println(">>> repository contains static function definition only");
}
}
server.setRepository(cR);
JCoServerFunctionHandler requestHandler = new StfcConnectionHandler();
DefaultServerHandlerFactory.FunctionHandlerFactory factory = new DefaultServerHandlerFactory.FunctionHandlerFactory();
factory.registerHandler(fT.getName(), requestHandler);
server.setCallHandlerFactory(factory);
server.start();
}
/*
* 該類用於在ABAP進行事務調用(CALL FUNCTION func IN BACKGROUND TASK DESTINATION dest)
* 時, Java端需要實時告訴ABAP端目前事務處理的情況(狀態),即Java與ABAP之間的事務狀態的交流
*/
static class MyTIDHandler implements JCoServerTIDHandler {
// 存儲事務狀態信息
Map<String, TIDState>availableTIDs = new Hashtable<String, TIDState>();
// 18662702337
// 當一個事務性RFM從ABAP端進行調用時,會觸發此方法
public boolean checkTID(JCoServerContext serverCtx, String tid) {
// This example uses a Hashtable to store status information.
// Normally, however,
// you would use a database. If the DB is down throw a
// RuntimeException at
// this point. JCo will then abort the tRFC and the R/3 backend will
// try again later.
System.out.println("TID Handler: checkTID for " + tid);
TIDState state = availableTIDs.get(tid);
if (state == null) {
availableTIDs.put(tid, TIDState.CREATED);
return true;
}
if (state == TIDState.CREATED || state == TIDState.ROLLED_BACK) {
return true;
}
return false;
// "true" means that JCo will now execute the transaction, "false"
// means
// that we have already executed this transaction previously, so JCo
// will
// skip the handleRequest() step and will immediately return an OK
// code to R/3.
}
// 事件提交時觸發
public void commit(JCoServerContext serverCtx, String tid) {
System.out.println("TID Handler: commit for " + tid);
// react on commit, e.g. commit on the database;
// if necessary throw a RuntimeException, if the commit was not
// possible
availableTIDs.put(tid, TIDState.COMMITTED);
}
// 事務回滾時觸發
public void rollback(JCoServerContext serverCtx, String tid) {
System.out.println("TID Handler: rollback for " + tid);
availableTIDs.put(tid, TIDState.ROLLED_BACK);
// react on rollback, e.g. rollback on the database
}
public void confirmTID(JCoServerContext serverCtx, String tid) {
System.out.println("TID Handler: confirmTID for " + tid);
try {
// clean up the resources
}
// catch(Throwable t) {} //partner(代碼ABAP對方) won't react on an
// exception at
// this point
finally {
availableTIDs.remove(tid);
}
}
private enum TIDState {
CREATED, COMMITTED, ROLLED_BACK, CONFIRMED;
}
}
/**
* Follow server example demonstrates how to implement the support for tRFC
* calls, calls executed BACKGROUND TASK. At first we write am
* implementation for JCoServerTIDHandler interface. This implementation is
* registered by the server instance and will be used for each call send in
* "background task". Without such implementation JCo runtime deny any tRFC
* calls. See javadoc for interface JCoServerTIDHandler for details.
*/
// 支持事務性調用,但究竟如果使用,有什么作用不清楚!!!
static void transactionRFCServer() {
JCoServer server;
try {
server = JCoServerFactory.getServer(SERVER_NAME1);
} catch (JCoException ex) {
throw new RuntimeException("Unable to create the server "
+ SERVER_NAME1 + " because of " + ex.getMessage(), ex);
}
JCoServerFunctionHandler stfcConnectionHandler = new StfcConnectionHandler();
DefaultServerHandlerFactory.FunctionHandlerFactory factory = new DefaultServerHandlerFactory.FunctionHandlerFactory();
factory.registerHandler("STFC_CONNECTION", stfcConnectionHandler);
server.setCallHandlerFactory(factory);
// ***添加事務處理器
myTIDHandler = new MyTIDHandler();
server.setTIDHandler(myTIDHandler);
server.start();
}
public static void main(String[] a) {
simpleServer();
// staticRepository();
//transactionRFCServer();
}
}
帶狀態訪問
還是以計數器函數為例,參照Java多線程調用有(無)狀態RFM章節,只是Z_INCREMENT_COUNTER、Z_GET_COUNTER兩個函數是在Java服務器端實現,而不ABAP中創建的函數。
當ABAP客戶端多次調用Z_INCREMENT_COUNTER函數計數后,最后調用Z_GET_COUNTER函數來獲取計數器結果,這一過程要多次調用Java端RFC函數,這要保證是在同一會話中完成的
ABAP客戶端代碼(可拷貝兩份進行測試)
DATA value TYPE i.
DATA loops TYPE i VALUE 5.
"多次調用RFM,表示該任務LUW單元有多步,但需要保存同一LUW單元在Java端為同一會話中執行
DO loops TIMES.
CALL FUNCTION 'Z_INCREMENT_COUNTER' DESTINATION 'JCOTEST'.
wait UP TO 10 SECONDS.
ENDDO.
CALL FUNCTION 'Z_GET_COUNTER' DESTINATION 'JCOTEST'
IMPORTING
get_value = value.
IF value <> loops.
WRITE: / 'Error expecting ', loops, ', but get ', value, ' as counter value'.
ELSE.
WRITE: / 'success!! COUNTER =', value.
ENDIF.
Java服務端代碼
import java.io.File;
import java.io.FileOutputStream;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import com.sap.conn.jco.JCo;
import com.sap.conn.jco.JCoCustomRepository;
import com.sap.conn.jco.JCoDestinationManager;
import com.sap.conn.jco.JCoException;
import com.sap.conn.jco.JCoFunction;
import com.sap.conn.jco.JCoFunctionTemplate;
import com.sap.conn.jco.JCoListMetaData;
import com.sap.conn.jco.JCoMetaData;
import com.sap.conn.jco.ext.DestinationDataProvider;
import com.sap.conn.jco.ext.ServerDataProvider;
import com.sap.conn.jco.server.JCoServer;
import com.sap.conn.jco.server.JCoServerContext;
import com.sap.conn.jco.server.JCoServerFactory;
import com.sap.conn.jco.server.JCoServerFunctionHandler;
import com.sap.conn.jco.server.JCoServerFunctionHandlerFactory;
public class StatefulServerExample {
static String SERVER_NAME1 = "SERVER";
static String DESTINATION_NAME1 = "ABAP_AS_WITHOUT_POOL";
static String DESTINATION_NAME2 = "ABAP_AS_WITH_POOL";
static {
JCo.setTrace(4, null);// 打開調試
Properties connectProperties = new Properties();
// ******直連
connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST,
"192.168.111.137");
connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "00");
connectProperties
.setProperty(DestinationDataProvider.JCO_CLIENT, "800");
connectProperties.setProperty(DestinationDataProvider.JCO_USER,
"SAPECC");
connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD,
"sapecc60");
connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "en");
createDataFile(DESTINATION_NAME1, "jcoDestination", connectProperties);
// ******連接池
connectProperties.setProperty(
DestinationDataProvider.JCO_POOL_CAPACITY, "3");
connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT,
"10");
createDataFile(DESTINATION_NAME2, "jcoDestination", connectProperties);
// ******JCo sever
Properties servertProperties = new Properties();
servertProperties.setProperty(ServerDataProvider.JCO_GWHOST,
"192.168.111.137");
// TCP服務sapgw是固定的,后面的00就是SAP實例系統編號,也可直接是端口號(端口號可以在
// etc/server文件中找sapgw00所對應的端口號)
servertProperties.setProperty(ServerDataProvider.JCO_GWSERV, "sapgw00");
servertProperties
.setProperty(ServerDataProvider.JCO_PROGID, "JCO_TEST");
servertProperties.setProperty(ServerDataProvider.JCO_REP_DEST,
DESTINATION_NAME2);
servertProperties.setProperty(ServerDataProvider.JCO_CONNECTION_COUNT,
"2");
createDataFile(SERVER_NAME1, "jcoServer", servertProperties);
}
static void createDataFile(String name, String suffix, Properties properties) {
File cfg = new File(name + "." + suffix);
if (!cfg.exists()) {
try {
FileOutputStream fos = new FileOutputStream(cfg, false);
properties.store(fos, "for tests only !");
fos.close();
} catch (Exception e) {
throw new RuntimeException(
"Unable to create the destination file "
+ cfg.getName(), e);
}
}
}
static class MyFunctionHandlerFactory implements
JCoServerFunctionHandlerFactory {
// 會話上下文,其實就是每一個ABAP端都有這樣一個全局對象,同一ABAP客戶端共享着同一個這個的會話上下文對象
class SessionContext {
// 用於存儲(緩存)會話中的數據
Hashtable<String, Object>cachedSessionData = new Hashtable<String, Object>();
// 其他會話數據......
}
// 每個任務單元LUW都有自己的會話上下文,Key為任務會話ID(比如同時多個ABAP客戶端調用當前同一Java服務端時,
// 會產會多個任務會話,而且每次調用時,同一ABAP客戶端所傳遞過來的會話ID是同一個)
private Map<String, SessionContext>statefulSessions = new Hashtable<String, SessionContext>();
private ZGetCounterFunctionHandler zGetCounterFunctionHandler = new ZGetCounterFunctionHandler();
private ZIncrementCounterFunctionHandler zIncrementCounterFunctionHandler = new ZIncrementCounterFunctionHandler();
public JCoServerFunctionHandler getCallHandler(
JCoServerContext serverCtx, String functionName) {
JCoServerFunctionHandler handler = null;
if (functionName.equals("Z_INCREMENT_COUNTER")) {
handler = zIncrementCounterFunctionHandler;
} else if (functionName.equals("Z_GET_COUNTER")) {
handler = zGetCounterFunctionHandler;
}
if (handler instance of StatefulFunctionModule) {
SessionContext cachedSession;
// 如果是某任務LUW中第一次調用時,則serverCtx服務上下文為非狀態,需設置為狀態調用
if (!serverCtx.isStatefulSession()) {
// 設置為狀態調用,這樣在有狀態調用的情況下,上下文中會攜帶會話ID
serverCtx.setStateful(true);
cachedSession = new SessionContext();// 新建會話
// 將會話存儲在映射表中,以便某個任務里每次遠程調用都可以拿到同一會話
statefulSessions.put(serverCtx.getSessionID(),
cachedSession);
} else {// 非第一次調用
cachedSession = statefulSessions.get(serverCtx
.getSessionID());
if (cachedSession == null)
throw new RuntimeException(
"Unable to find the session context for session id "
+ serverCtx.getSessionID());
}
// 為遠程函數處理器設置會話
((StatefulFunctionModule) handler)
.setSessionData(cachedSession.cachedSessionData);
return handler;
}
// null leads to a system failure on the ABAP side
retur nnull;
}
public void sessionClosed(JCoServerContext serverCtx, String message,
boolean error) {
System.out.println("Session " + serverCtx.getSessionID()
+ " was closed " + (error ? message : "by SAP system"));
statefulSessions.remove(serverCtx.getSessionID());
}
}
static abstract class StatefulFunctionModule implements
JCoServerFunctionHandler {
Hashtable<String, Object>sessionData;
public void setSessionData(Hashtable<String, Object> sessionData) {
this.sessionData = sessionData;
}
}
// 獲取計數器值
static class ZGetCounterFunctionHandler extends StatefulFunctionModule {
public void handleRequest(JCoServerContext serverCtx,
JCoFunction function) {
System.out.println("ZGetCounterFunctionHandler: return counter");
Integer counter = (Integer) sessionData.get("COUNTER");
if (counter == null) {
function.getExportParameterList().setValue("GET_VALUE", 0);
} else {
function.getExportParameterList().setValue("GET_VALUE",
counter.intValue());
}
}
}
static class ZIncrementCounterFunctionHandler extends
StatefulFunctionModule {
public void handleRequest(JCoServerContext serverCtx,
JCoFunction function) {
System.out
.println("ZIncrementCounterFunctionHandler: increase counter");
Integer counter = (Integer) sessionData.get("COUNTER");
if (counter == null)
// 向會話上下文中存儲存儲計數結果:如果是第一次調用計數功能,則為1
sessionData.put("COUNTER", new Integer(1));
else
// 向會話上下文中存儲存儲計數結果:如果不是第一次調用計數功能,則在原有計數器值基礎之上加1
sessionData.put("COUNTER", new Integer(counter.intValue() + 1));
}
}
public static void main(String[] args) {
// Z_GET_COUNTER、Z_INCREMENT_COUNTER兩個函數無需在ABAP中定義,通過Java注冊了這兩個函數對象
JCoListMetaData expList = JCo.createListMetaData("EXPORT");
expList.add("GET_VALUE", JCoMetaData.TYPE_INT, 2, 2, 0, null, null,
JCoListMetaData.EXPORT_PARAMETER, null, null);
expList.lock();
JCoFunctionTemplate fT1 = JCo.createFunctionTemplate("Z_GET_COUNTER",
null, expList, null, null, null);
JCoCustomRepository cR = JCo
.createCustomRepository("MyCustomRepository");
cR.addFunctionTemplateToCache(fT1);
JCoFunctionTemplate fT2 = JCo.createFunctionTemplate(
"Z_INCREMENT_COUNTER", null, null, null, null, null);
cR.addFunctionTemplateToCache(fT2);
String serverName = "SERVER";
JCoServer server;
try {
server = JCoServerFactory.getServer(serverName);
} catch (JCoException ex) {
throw new RuntimeException("Unable to create the server "
+ serverName + ", because of " + ex.getMessage(), ex);
}
String repDest = server.getRepositoryDestination();
if (repDest != null) {
try {
cR.setDestination(JCoDestinationManager.getDestination(repDest));
} catch (JCoException e) {
e.printStackTrace();
System.out
.println(">>> repository contains static function definition only");
}
}
server.setRepository(cR);
MyFunctionHandlerFactory factory = new MyFunctionHandlerFactory();
server.setCallHandlerFactory(factory);
server.start();
}
}
JCo RFC函數異常總結
異常:(103)RFC_ERROR_LOGON_FAILURE: ##.#####,####
原因:該異常一般是賬戶密碼錯誤導致,首先檢查賬號密碼(最好是大寫),其次很有可能是JCO的連接池配置文件在Server上properties文件沒有實時的更新過來,導致還在使用原登錄配置連接,該文件一般在Server的/bin目錄下面。
其他一些異常的搜集總結:
(103) RFC_ERROR_LOGON_FAILURE: Name or password is incorrect (repeat logon)
用戶名密碼可能是錯誤或者用戶無權限,確認用戶,必要時聯系SAP負責人,檢查用戶
(101) RFC_ERROR_PROGRAM: Missing R3NAME=... or ASHOST=... in connect_param in RfcOpenEx
call信息沒有填寫完整,檢查配置文件各個SAP配置信息是否完整
com.sap.mw.jco.JCO$Exception: (102) RFC_ERROR_COMMUNICATION: Connect to SAP gateway failed
IP地址錯誤
(102) RFC_ERROR_COMMUNICATION:Connect to message server failed
組權限訪問 server文件沒更新
(103) RFC_ERROR_LOGON_FAILURE: ## 502 ########
端口號錯誤報錯信息
(103) RFC_ERROR_LOGON_FAILURE: Timeout
超時
(104) RFC_ERROR_SYSTEM_FAILURE: Error in module RSQL of the database interface.執行函數
(104) RFC_ERROR_SYSTEM_FAILURE: An error occurred when receiving a complex parameter.
(106) JCO_ERROR_RESOURCE: Trying to access row values in a table which does not have any rows yet
執行函數,函數的問題
(106) JCO_ERROR_RESOURCE: Trying to access row values in a table which does not have any rows yet
返回的表沒有值.那個表連第一行都沒有,取不到
(104) RFC_ERROR_SYSTEM_FAILURE: Syntax error in program SAPMV50A
語法錯誤
(106) JCO_ERROR_RESOURCE: Trying to access row values in a table which does not ha:ve any rows yet
找不到行
(122)JCO_ERROR_CONVERSION: Integer '4234243' has to many digits at field PO_ITEM
輸入參數不能插入SAP函數輸入字段中