Второе приложение SAP JCO
Ранее в этом году я разработал реализацию SAP JCO CustomDestinationProvider для одного из моих приложений Spring MVC tomcat. В моем приложении я использую эту реализацию для вызова BAPI в моей системе SAP R/3 для получения данных.
Сейчас я работаю над вторым приложением Spring MVC tomcat, для которого я хочу вызвать BAPI в моей системе SAP R/3 для получения данных. Это будет другой BAPI, который я буду вызывать, поэтому это будут другие данные, которые я буду получать. Поскольку это другое приложение, вызывающее другой BAPI, я хочу использовать другого пользователя системы SAP в своих конфигурациях. Это новое приложение будет работать на том же физическом сервере Tomcat, что и первое приложение.
Мой вопрос заключается в том, следует ли мне разработать другую реализацию SAP JCO CustomDestinationProvider для этого нового приложения или мне следует как-то повторно использовать первую реализацию? Если ответ заключается в том, что мне следует разработать другую реализацию для этого нового приложения, я бы ожидал, что я буду разрабатывать другую реализацию для каждого нового приложения Spring MVC tomcat, которое я разрабатываю и которое должно взаимодействовать с SAP. Это правильное мышление?
Если я делаю другую реализацию для этого моего нового приложения, должен ли я использовать то же имя назначения (ABAP_AS) в коде, или я должен использовать другое имя? Ниже приведен код для моей первой реализации CustomDestinationDataProvider:
public class CustomDestinationDataProvider {
public class MyDestinationDataProvider implements DestinationDataProvider {
private DestinationDataEventListener eL;
private HashMap<String, Properties> secureDBStorage = new HashMap<String, Properties>();
public Properties getDestinationProperties(String destinationName) {
try {
Properties p = secureDBStorage.get(destinationName);
if(p!=null) {
if(p.isEmpty())
throw new DataProviderException(DataProviderException.Reason.INVALID_CONFIGURATION, "destination configuration is incorrect", null);
return p;
}
return null;
} catch(RuntimeException re) {
throw new DataProviderException(DataProviderException.Reason.INTERNAL_ERROR, re);
}
}
public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {
this.eL = eventListener;
}
public boolean supportsEvents() {
return true;
}
public void changeProperties(String destName, Properties properties) {
synchronized(secureDBStorage) {
if(properties==null) {
if(secureDBStorage.remove(destName)!=null)
eL.deleted(destName);
} else {
secureDBStorage.put(destName, properties);
eL.updated(destName); // create or updated
}
}
}
}
public ArrayList<String> executeSAPCall(Properties connectProperties, ArrayList<String> partnumbers) throws Exception {
String destName = "ABAP_AS";
SAPDAO sapDAO = new SAPDAO();
ArrayList<MaterialBean> searchResults = new ArrayList<MaterialBean>();
MyDestinationDataProvider myProvider = new MyDestinationDataProvider();
boolean destinationDataProviderRegistered = com.sap.conn.jco.ext.Environment.isDestinationDataProviderRegistered();
JCoDestination dest;
try {
if (!destinationDataProviderRegistered) {
com.sap.conn.jco.ext.Environment.registerDestinationDataProvider(myProvider);
myProvider.changeProperties(destName, connectProperties);
}
} catch(IllegalStateException providerAlreadyRegisteredException) {
logger.error("executeSAPCall: providerAlreadyRegisteredException!");
}
try {
dest = JCoDestinationManager.getDestination(destName);
searchResults = sapDAO.searchSAP(dest, partnumbers);
} catch(JCoException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return searchResults;
}
}
Если ответ заключается в том, что мне не нужно реализовывать другой CustomDestinationDataProvider для моего второго приложения, какие еще соображения мне нужно учитывать?
2 ответа
Я понял это! Я обнаружил два разных способа реализации CustomDestinationDataProvider, чтобы я мог использовать несколько мест назначения.
Что-то, что я сделал, что помогло с обоими моими различными решениями, это изменил метод в CustomDestinationDataProvider, который создает экземпляр внутреннего класса MyDestinationDataProvider, так что вместо возврата ArrayList он возвращает JCoDestination. Я изменил имя этого метода с executeSAPCall на getDestination.
Первый способ, который я обнаружил и позволил мне использовать несколько мест назначения, успешно меняя места назначения, заключался в том, чтобы ввести переменную класса для MyDestinationDataProvider, чтобы я мог сохранить свою экземплярную версию. Обратите внимание, что для этого решения класс CustomDestinationDataProvider по-прежнему встроен в код моего приложения Java.
Я обнаружил, что это решение работает только для одного приложения. Я не смог использовать этот механизм в нескольких приложениях на одном и том же сервере tomcat, но, по крайней мере, я наконец смог успешно переключить места назначения. Вот код для CustomDestinationDataProvider.java для этого первого решения:
public class CustomDestinationDataProvider {
private MyDestinationDataProvider gProvider; // class version of MyDestinationDataProvider
public class MyDestinationDataProvider implements DestinationDataProvider {
private DestinationDataEventListener eL;
private HashMap<String, Properties> secureDBStorage = new HashMap<String, Properties>();
public Properties getDestinationProperties(String destinationName) {
try {
Properties p = secureDBStorage.get(destinationName);
if(p!=null) {
if(p.isEmpty())
throw new DataProviderException(DataProviderException.Reason.INVALID_CONFIGURATION, "destination configuration is incorrect", null);
return p;
}
return null;
} catch(RuntimeException re) {
System.out.println("getDestinationProperties: Exception detected!!! message = " + re.getMessage());
throw new DataProviderException(DataProviderException.Reason.INTERNAL_ERROR, re);
}
}
public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {
this.eL = eventListener;
}
public boolean supportsEvents() {
return true;
}
public void changeProperties(String destName, Properties properties) {
synchronized(secureDBStorage) {
if(properties==null) {
if(secureDBStorage.remove(destName)!=null) {
eL.deleted(destName);
}
} else {
secureDBStorage.put(destName, properties);
eL.updated(destName); // create or updated
}
}
}
}
public JCoDestination getDestination(String destName, Properties connectProperties) {
MyDestinationDataProvider myProvider = new MyDestinationDataProvider();
boolean destinationDataProviderRegistered = com.sap.conn.jco.ext.Environment.isDestinationDataProviderRegistered();
if (!destinationDataProviderRegistered) {
try {
com.sap.conn.jco.ext.Environment.registerDestinationDataProvider(myProvider);
gProvider = myProvider; // save our destination data provider in the class var
} catch(IllegalStateException providerAlreadyRegisteredException) {
throw new Error(providerAlreadyRegisteredException);
}
} else {
myProvider = gProvider; // get the destination data provider from the class var.
}
myProvider.changeProperties(destName, connectProperties);
JCoDestination dest = null;
try {
dest = JCoDestinationManager.getDestination(destName);
} catch(JCoException e) {
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
}
return dest;
}
}
Это код в моем классе сервлетов, который я использую для создания экземпляра и вызова CustomDestinationDataProvider в коде моего приложения:
CustomDestinationDataProvider cddp = new CustomDestinationDataProvider();
SAPDAO sapDAO = new SAPDAO();
Properties p1 = getProperties("SAPSystem01");
Properties p2 = getProperties("SAPSystem02");
try {
JCoDestination dest = cddp.getDestination("SAP_R3_USERID_01", p1); // establish the first destination
sapDAO.searchEmployees(dest, searchCriteria); // call the first BAPI
dest = cddp.getDestination("SAP_R3_USERID_02", p2); // establish the second destination
sapDAO.searchAvailability(dest); // call the second BAPI
} catch (Exception e) {
e.printStackTrace();
}
Опять же, это решение работает только в одном приложении. Если вы реализуете этот код непосредственно в нескольких приложениях, первое приложение, которое вызывает этот код, получает ресурс, а другое выдает ошибку.
Второе решение, которое я придумал, позволяет нескольким Java-приложениям одновременно использовать класс CustomDestinationDataProvider. Я вырвал класс CustomDestinationDataProvider из кода своего приложения и создал для него отдельное приложение Java Spring (не веб-приложение) с целью создания jar-файла. Затем я преобразовал внутренний класс MyDestinationDataProvider в синглтон. Вот код для одиночной версии CustomDestinationDataProvider:
public class CustomDestinationDataProvider {
public static class MyDestinationDataProvider implements DestinationDataProvider {
////////////////////////////////////////////////////////////////////
// The following lines convert MyDestinationDataProvider into a singleton. Notice
// that the MyDestinationDataProvider class has now been declared as static.
private static MyDestinationDataProvider myDestinationDataProvider = null;
private MyDestinationDataProvider() {
}
public static MyDestinationDataProvider getInstance() {
if (myDestinationDataProvider == null) {
myDestinationDataProvider = new MyDestinationDataProvider();
}
return myDestinationDataProvider;
}
////////////////////////////////////////////////////////////////////
private DestinationDataEventListener eL;
private HashMap<String, Properties> secureDBStorage = new HashMap<String, Properties>();
public Properties getDestinationProperties(String destinationName) {
try {
Properties p = secureDBStorage.get(destinationName);
if(p!=null) {
if(p.isEmpty())
throw new DataProviderException(DataProviderException.Reason.INVALID_CONFIGURATION, "destination configuration is incorrect", null);
return p;
}
return null;
} catch(RuntimeException re) {
throw new DataProviderException(DataProviderException.Reason.INTERNAL_ERROR, re);
}
}
public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {
this.eL = eventListener;
}
public boolean supportsEvents() {
return true;
}
public void changeProperties(String destName, Properties properties) {
synchronized(secureDBStorage) {
if(properties==null) {
if(secureDBStorage.remove(destName)!=null) {
eL.deleted(destName);
}
} else {
secureDBStorage.put(destName, properties);
eL.updated(destName); // create or updated
}
}
}
}
public JCoDestination getDestination(String destName, Properties connectProperties) throws Exception {
MyDestinationDataProvider myProvider = MyDestinationDataProvider.getInstance();
boolean destinationDataProviderRegistered = com.sap.conn.jco.ext.Environment.isDestinationDataProviderRegistered();
if (!destinationDataProviderRegistered) {
try {
com.sap.conn.jco.ext.Environment.registerDestinationDataProvider(myProvider);
} catch(IllegalStateException providerAlreadyRegisteredException) {
throw new Error(providerAlreadyRegisteredException);
}
}
myProvider.changeProperties(destName, connectProperties);
JCoDestination dest = null;
try {
dest = JCoDestinationManager.getDestination(destName);
} catch(JCoException ex) {
ex.printStackTrace();
throw ex;
} catch (Exception ex) {
ex.printStackTrace();
throw ex;
}
return dest;
}
}
После помещения этого кода в приложение jar-файла и создания файла jar (я называю его JCOConnector.jar), я поместил файл jar в путь к классам общей библиотеки моего сервера tomcat и перезапустил сервер tomcat. В моем случае это был /opt/tomcat/shared/lib. Проверьте файл /opt/tomcat/conf/catalina.properties для строки shared.loader, где находится путь к классу вашей общей библиотеки. Моя выглядит так:
shared.loader=\
${catalina.home}/shared/lib\*.jar,${catalina.home}/shared/lib
Я также поместил копию этого файла JAR в папку "C:\Users\userid\Documents\jars" на моей рабочей станции, чтобы код тестового приложения мог видеть код в банке и компилировать. Затем я сослался на эту копию файла jar в моем файле pom.xml в обоих моих тестовых приложениях:
<dependency>
<groupId>com.mycompany</groupId>
<artifactId>jcoconnector</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>C:\Users\userid\Documents\jars\JCOConnector.jar</systemPath>
</dependency>
После добавления этого в файл pom.xml я щелкнул правой кнопкой мыши по каждому проекту, выбрал Maven -> Обновить проект..., а затем снова щелкнул правой кнопкой мыши по каждому проекту и выбрал "Обновить". Что-то очень важное, что я узнал, это не добавлять копию JCOConnector.jar напрямую ни в один из моих тестовых проектов. Причина этого в том, что я хочу использовать код из файла jar в /opt/tomcat/shared/lib/JCOConnector.jar. Затем я создал и развернул каждое из моих тестовых приложений на сервере Tomcat.
Код, который вызывает мою разделяемую библиотеку JCOConnector.jar в моем первом тестовом приложении, выглядит следующим образом:
CustomDestinationDataProvider cddp = new CustomDestinationDataProvider();
JCoDestination dest = null;
SAPDAO sapDAO = new SAPDAO();
Properties p1 = getProperties("SAPSystem01");
try {
dest = cddp.getDestination("SAP_R3_USERID_01", p1);
sapDAO.searchEmployees(dest);
} catch (Exception ex) {
ex.printStackTrace();
}
Код во втором тестовом приложении, которое вызывает мою разделяемую библиотеку JCOConnector.jar, выглядит следующим образом:
CustomDestinationDataProvider cddp = new CustomDestinationDataProvider();
JCoDestination dest = null;
SAPDAO sapDAO = new SAPDAO();
Properties p2 = getProperties("SAPSystem02");
try {
dest = cddp.getDestination("SAP_R3_USERID_02", p2);
sapDAO.searchAvailability(dest);
} catch (Exception ex) {
ex.printStackTrace();
}
Я знаю, что я пропустил много шагов, связанных с первой установкой библиотеки SAP JCO 3, установленной на вашей рабочей станции и сервере. Я действительно надеюсь, что это поможет по крайней мере еще одному человеку преодолеть трудности, связанные с попытками получить несколько Java-приложений Spring MVC, говорящих с SAP на одном сервере.
Вы можете зарегистрировать только один DestinationDataProvider
поэтому тот, который вы установили, должен иметь возможность обрабатывать оба (или более) разных соединения. Для этого вам нужны уникальные имена для каждого соединения, т.е. destName
не может быть фиксированным значением ABAP_AS
необходимо создать один для каждого соединения.
Ваша текущая реализация провайдера выглядит хорошо для меня, но ваш метод при вызове RFC смешивает создание соединения и фактический вызов RFC в моих глазах. ИМХО, вам следует разделить первый метод на собственный метод, чтобы вы могли вызывать его из других частей вашего приложения, например, чтобы выполнять другие действия, кроме RFC-вызовов.