I've written a Spring MVC (Spring framework 4.1.1) java 1.8 application that successfully connects to SAP using the sapjco3.jar driver, and I've accomplished this using the CustomDestinationDataProvider technique. I then use this drive to call RFCs in my SAP R/3 system. The java code is executed via api call from an AngularJS front end application.
Something that I've discovered occuring about 5% of the time that the call to SAP happens is the following error occurs:
NestedServletException: Handler processing failed; nested exception is
java.lang.Error: java.lang.IllegalStateException: DestinationDataProvider
already registered
Here's the contents of my CustomDestinationDataProvider.java file:
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<MaterialBean> executeAvailabilityCall(Properties connectProperties, String searchString) {
String destName = "ABAP_AS";
SAPDAO sapDAO = new SAPDAO();
ArrayList<MaterialBean> searchResults = new ArrayList<MaterialBean>();
MyDestinationDataProvider myProvider = new MyDestinationDataProvider();
JCoDestination dest;
try {
com.sap.conn.jco.ext.Environment.registerDestinationDataProvider(myProvider);
} catch(IllegalStateException providerAlreadyRegisteredException) {
}
myProvider.changeProperties(destName, connectProperties);
try {
dest = JCoDestinationManager.getDestination(destName);
searchResults = sapDAO.searchAvailability(dest, searchString);
} catch(JCoException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
myProvider.changeProperties(destName, null);
try {
com.sap.conn.jco.ext.Environment.unregisterDestinationDataProvider(myProvider);
} catch(IllegalStateException providerAlreadyRegisteredException) {
throw new Error(providerAlreadyRegisteredException);
}
return searchResults;
} // end method executeAvailabilityCall()
} // end class CustomDestinationProvider()
My guess is that multiple api calls are occuring at the same time, and once the first query registers the destination data provider, the subsequent queries, which try to also register the destination data provider, fail because they are using the same value for 'destName' in the executeAvailabilityCall method.
Upon first glace, it seems to me like I should use a dynamic value for the destName variable instead of just using "ABAP_AS" for all queries. In other words, I should change the following line:
String destName = "ABAP_AS";
to something like this:
String destName = "ABAP_AS_" + LocalDateTime.now();
This would guarantee a unique value for the destName variable, thus a unique destination provider name.
Any thoughts on the wisdom of trying this? If this is not a good idea, what other solution would be worth exploring?
Yes, you should use multiple unique destination names for your various logon Properties configuration sets. Your class MyDestinationDataProvider is already implemented that way. But why putting a timestamp into the destination name? Why not simply using a destination name schema like "TargetSystem_<SID>_with_<username>"?
Regarding your exception, simply register MyDestinationDataProvider only once and do not permanently register and unregister it. This is not how JCo expects this to be implemented. Quote from the JCo JavaDoc at com.sap.conn.jco.ext.DestinationDataProvider
:
Only one implementation of DestinationDataProvider can be registered. For registering another implementation the infrastructure has first to unregister the implementation that is currently registered. It is not recommended to permanently exchange DestinationDataProvider registrations. The one registered instance should globally manage all destination configurations for the whole infrastructure environment.