cancel
Showing results for 
Search instead for 
Did you mean: 

SAP JCO 3, DestinationDataProvider, Environment and tomcat

Former Member
0 Kudos

Hi all,

So I am upgrading from SAP JCO 2 to 3 and have implemented my own DestinationDataProvider. I register it with Environment.registerDestinationDataProvider. This is in a tomcat environment with many WARs, many of which connect to SAP using JCO and hold their own connection details. I've started getting IllegalStateException:

DestinationDataProvider already registered [com.hre.sapjco.rfc.SapJcoDestinationDataProvider]

and then came across this:

The question is that I find has not been answered adequately is how do I do this in an environment with many wars.

Environment has no way of returning the registered DestinationDataProvider in order to add new connection details. I cannot just ignore the error because then the registered DestinationDataProvider will not have my connection properties.

Is it even correct that Environment is shared across the whole tomcat or am I experiencing a different problem? How is it doing this? Is there any way around this?

Regards,

Melion

Accepted Solutions (1)

Accepted Solutions (1)

HAL9000
Product and Topic Expert
Product and Topic Expert
0 Kudos

The SAP Java Connector 3 design is following the principle for the separation of concerns which are:

  1. developing the business application (focus onto business logic)
  2. integration into the runtime environment (doing the infrastructure implementation: database specific [secure storage], application server specific [session management], etc.)
  3. administrate the solution (install and configure the application)

These 3 areas were separated from each other so that each part can be handled adequately without having knowledge about the other parts and implementations. The business logic shall be independent from the infrastructure implementation / runtime environment and should not contain configuration data.

For example, the administrator of the overall solution shall be able to configure the product according to the use case, landscape, user sizing and so on without the need to adapt the coding or to redeploy the business application after each modification. Furthermore the application should be moveable from one runtime environment / infrastructure to another without changing the business application.

Let me quote the JCo documentation (from the package com.sap.conn.jco.ext description):


Provides all interfaces that are offered for embedding JCo into an application server. In addition to that the Environment class contains all necessary methods for dealing with the infrastructure components. Typically, only infrastructure programmers will have the need to deal with that package. Business application logic typically can be developed without knowing the infrastructure. Details are available from the single interface descriptions.

Recommendations:

  • When deploying business application artifacts to some arbitrary application server, you should rely on the infrastructure components being available already and not package infrastructure embedding components together with them. Actually, with JCo 3.0 infrastructure deployables should be kept completely seperate from business application logic deployables. Thus, it won't happen that there are conflicts with e.g. two implementations/instances of a DestinationDataProvider. Only if there is no fitting provider available, you could deploy a seperate archive with the infrastructure component, for which you have a general implementation that is fitting to your needs. Or even better, you delegate it to the administrator who can decide for the infrastructure implementation he likes best. If the default implementation included in JCo is fitting well enough, you even don't have to deploy anything. Never require to have always your specific implementation installed on an arbitrary application server.
  • When implementing several applications that use JCo for accessing an SAP system on top of the same application runtime, you should make sure that you think about who is taking care of the infrastructure embedding for JCo. This should be only a single group.

So to your problem:

The implementation of the DestinationDataProvider is part of the infrastructure implementation and should be deployed with a separate bundle. This implementation should have an open API and/or GUI for importing / maintaining the destination data.

To give an example: in the SAP NetWeaver Application Server Java this interface is implemented by the integrated Destination Service which offers a programming API and a GUI in the NetWeaver Administrator for maintaining RFC destination data and logon parameters.

Finally, all JCo resources are shared amongst all applications running in the same application server process. So there can only be one DestinationDataProvider registered at the JCo runtime.

Best regards,

Stefan

Former Member
0 Kudos

Hi Stefan,

thanks for the reply! I don't really want to get into a discussion about it and I see you are a SAP employee, but that seems like bad design to me. I think it would have been much cleaner to provide an elegant api / interface with a sensible default implementation and leave the rest up to the consumer of the library. I certainly don't need this and it could be a real pain to program around it.

One question though: is this "feature" possible because of the fact that sapjco3.jar is deployed in the TOMCAT/lib directory as recommended? If I remove this to a war local lib dir could I keep the Environment  on a per war basis? Will anything else be negatively affected?

Regards,

David

HAL9000
Product and Topic Expert
Product and Topic Expert
0 Kudos

Hi David,

if you would like to thank me, please mark the reply as the "Correct Answer" if it answered your question, or mark it at least as a helpful one - regardless if you like its ontent or not (there's a separate button for liking posts).

You may of course have the opinion that the separation of concerns principle is bad design, but I disagree. There are also developers who do not like the model-view-controller architectural pattern, or object oriented programming as a whole. But I think, once one has understood this, one will also see the advantages.

Yes, if you are sole programmer that has to take care about all alone anyway, the separation of concerns indeed requires a little bit more effort first, but one will see the benefits if some maintenance is needed in the future or something fundamental needs to be changed (e.g. the underlying database for storing the user logon data needs to be exchanged or simply it should be replaced by an LDAP service). With the JCo3 design you just have to adapt your infrastructure implementation project and not all your numerous deployed different applications. There are several more advantages, but as you desired, this should not be an architectural discussion, you only asked how to work with the DestinationDataProvider.

I tried to give you the answer in my last reply. And by the way, there IS a default implementation of the DestinationDataProvider that works with local property files, but this is only meant to be used for quick development purposes and not for productive use. Once there is a registration of some own DestinationDataProvider instance this one is used globally in the whole process (the application server) and also cannot be retrieved from the JCo environment by foreign applications. Remember: the idea with the separation of concerns is that the business application shall not depend on any specific infrastructure implementation and it shall not contain the technical configuration data which ABAP application server to connect to (this is the task of an administrator). The business application only needs to know the logical destination name and shall focus on the business logic instead (i.e. which remote function module shall be called with what parameter data?).

Anyway, there is no software concept that cannot be broken if one really wants to. This is how the quick and dirty solution would look like:

Develop a DestinationDataProvider implementation class that offers two additional methods:

1. public static <OwnDestinationDataProviderClass> singleton() : for getting your single object instance of this class

2. public void setDestinationProperties(java.lang.String destinationName, java.util.Properties props) : for storing arbitrary JCo properties mapped to a destination name

Then all applications need to have have a reference to the DestinationDataProvider application and so can get the global <OwnDestinationDataProviderClass> single instance (thus introducing the dependency to the infrastructure again) and can fill in their logon parameters to the internal data sink (thus mixing the administrative data [hard coded logon parameters] into the business application program again).

But just to mention one disadvantage of such an implementation: If you have deployed 10 applications which work with the same hard coded logon parameters and some connection data to the AS ABAP back-end changes (e.g. like a password or the hostname), then a developer is needed for modifying, recompiling and redeploying all your 10 different applications one by one. In contrast to this, how easy would it be for administrator to change the configuration data centrally once for all applications - even if the various applications stem from different developers and/or vendors?

And to your final question:

No, JCo3 is designed as a process global framework with reusing and minimizing the needed resources (like reusing connections and repository RFC meta data). You should not try to deploy JCo with each of your own applications. You will also probably fail if doing so, as the JCo native library cannot be loaded multiple times in one process.

Best regards,

Stefan

Former Member
0 Kudos

Thanks Stefan,

the original reply was somewhat helpful and the last one contained some nuggets of information interspersed with the arrogance Sorry man, but  I am a very experienced software developer and architect and don't need to be preached to about separation of concerns. I of course think that when done correctly is very useful as for MVC etc. This as far as I am concerned does not apply here. To force somebody to handle connection details pooling on a per process basis is just too opinionated. What I meant in my last post was that if it was left up to the user just to implement this and then decide themselves in which context to make it global would be much better design and give much more freedom. What you wrote in your post about the native jco lib being loaded per process is probably the reason anyway. The rest is just pouring sugar on this to make it seem like design. I have already implemented a process level singleton to get around this mess. Thanks for your help!

Regards,

David

HAL9000
Product and Topic Expert
Product and Topic Expert
0 Kudos

Handling connection details on a per process basis is the way how it is also realized in an SAP AS ABAP system (transaction SM59 can be seen as the DestinationDataProvider implementation) - so obviously not the worst approach as it has already proven to be a stable concept and manageable even in large scale installations with thousands of users.


Separation of concerns from the JCo perspective only works, if everyone sticks to it, not if some applications do and others don't. Freedom of coding was surely not the goal of the JCo3 API design. Amongst many other things one goal was the separation of the infrastructure implementation from the business logic. This was achieved with the com.sap.conn.jco.ext package interfaces, And this also makes sense especially in large environments with having several development teams and responsibilities. Ideally, the JCo infrastructure implementation would always come directly from the infrastructure vendor - like SAP does for its own NetWeaver Application Server Java.


Anyway, this is my final post in this thread. I don't like to argue with you here what is good or bad design, what only seems like design or what design is a mess. I only tried to help you with explaining the JCo design. Well, it got a bit lengthy because one cannot squeeze this part into a few words. And although I am an SAP employee, I'm not getting paid for answering questions in this forum. I've spent my free time for this here.


Good Luck!


Regards,

Stefan


MarkusTolksdorf
Product and Topic Expert
Product and Topic Expert
0 Kudos

Hi David,

IMHO, one can find good arguments for both the approach JCo3 is using and the one that you are talking about. However, there cannot be both at the same time. If a runtime environment wants do decide that there needs to be a global per-process setting it needs to be able to enforce this. This would break an application that wants do have its own storage anyway. Furthermore, the model to have a per-application storage also has some severe drawbacks from supportability perspective.
A decision had to be taken and from the robustness experience of AS ABAP with the global approach, the decision was taken to also enforce destination data providers to be global for JCo.

Best regards,

Markus

P.S.: I consider this more or like a discussion being similar to which music style is better: modern dance music versus classical orchestra music. There is no clear right and no clear wrong.

Former Member
0 Kudos

Hi Markus,

this will be my last reply here: we are not creating added value here

I don't agree with much of what you say: surprisingly

Firstly that what you write in your p.s. could be used to argue that there is no such thing as bad design. Hmmm...

Secondly I maintain that your other statement that both approaches are not compatible is also incorrect. With my approach it would also be possible to do process global connection details management, but by choice. I simply see no reason for SAP to enforce their idea of what constitutes "good" connection management / separation of concerns on me. I have worked with lots of client libraries over the years and cannot remember one doing this. If oracle did something like this in their ojdbc there would rightly be an uproar. I see no difference here.

Anyway, it is how it is. I've already fixed this and moved on. No biggie.

Regards,

David

MarkusTolksdorf
Product and Topic Expert
Product and Topic Expert
0 Kudos

Hi David,

with the PS I'm not saying that there is in general no bad design - I'm only referring to the concrete example with this statement: I believe that neither of the variants is a bad design, therefore the PS is valid.

I think it's ok if you disagree with the decision that was taken for JCo3 and that you don't like it, but IMHO by saying it is bad design you simply make it too easy for yourself. You might see only the disadvantages now in a concrete project, but not the advantages that the approach has. I guess, it would be easier to discuss this f2f, because forums sometimes are not interactive enough. But well, I agree with you, let's stop it here.

Best regards,

Markus

Answers (1)

Answers (1)

Former Member
0 Kudos

- Proposed Solution -

1. Write your own custom destination provider implementing SAP JCo3 DestinationDataProvider interface . The sample code is given below.

  • This custom DestinationProvider is a singleton class.


2. Create a jar file of this class... say MyJCo3.jar


3. Keep sapjco3.jar (standard SAP JCO3 library) and MyJCo3.jar in some custom path say C:\JCo3\MyJCo3.jar


4. Add this path to your Web server(Tomcat) CLASSPATH


5. Very Important: These jar files should not be part of your web apps (war files) deployed on Tomcat.

    Reason : If you have a singleton class and you run two webapps that use this class in Tomcat both webapps will get 2 different instances of this singleton in JVM running the Tomcat. But if your webapp will use a singleton from JRE or Tomcat shared libs, eg Runtime.getRuntime webapps will get the same instance of Runtime. This is because Tomcat uses individual class loaders for webapps. When a webapp class loader loads a class it first tries to find it on webapp class path, if the class is not found it asks parent class loader to load the class.

Reference: http://bit.do/bVcgG


6. Implemenation Code to be used in your application(s):

MyJcoDestinationProvider destinationDataProvider = MyJcoDestinationProvider.getInstance();


if (!Environment.isDestinationDataProviderRegistered()) {

         

            Environment.registerDestinationDataProvider(destinationDataProvider);

}


// here concatenate application/context name with Destination name.

// This is done to segregate the connection properties if two or more web applications have same destination name having different username and password.

destinationDataProvider.addDestinationByName(<YourAppName+DestinationName>,<Properties>);

try {

      dest = JCoDestinationManager.getDestination(<YourAppName+DestinationName>);

       

      repos = dest.getRepository();


    } catch (JCoException e) {

           

          throw new RuntimeException(e.toString());

    }

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

package com.my.jco;

import com.sap.conn.jco.ext.DestinationDataEventListener;

import com.sap.conn.jco.ext.DestinationDataProvider;

import java.util.HashMap;

import java.util.Map;

import java.util.Properties;

import java.util.logging.Level;

import java.util.logging.Logger;

public class MyJcoDestinationProvider implements DestinationDataProvider {

    private Map<String, Properties> destinationMap = new HashMap();

    private static final Logger LOG = Logger.getLogger(MyJcoDestinationProvider.class.getName());

    private static MyJcoDestinationProvider instance = null;

    public static MyJcoDestinationProvider getInstance() {

        if (instance == null) {

            instance = new MyJcoDestinationProvider();

        }

        return instance;

    }

    public MyJcoDestinationProvider() {

        if (instance == null) {

            instance = this;

        }

    }

    public Boolean addDestinationByName(String destName, Properties destProperties) {

        if (destinationMap.containsKey(destName)) {

            LOG.log(Level.WARNING, "Unable to load destination " + destName + ", already loaded.");

            return false;

        } else {

            destinationMap.put(destName, destProperties);

            LOG.log(Level.INFO, "Destination " + destName + " properties added/updated.");

            return true;

        }

    }

    public Map<String, Properties> getDestinations() {

        return destinationMap;

    }

    @Override

    public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {

        // nothing to do

    }

    @Override

    public boolean supportsEvents() {

        return false;

    }

    @Override

    public Properties getDestinationProperties(String destination) {

        Properties props = new Properties();

        if (destinationMap.containsKey(destination)) {

            props = destinationMap.get(destination);

            return props;

        } else {

            LOG.log(Level.WARNING, "Destination "+destination+" properties not found.");

            return null;

        }

    }

}

MarkusTolksdorf
Product and Topic Expert
Product and Topic Expert
0 Kudos

Hi Manoj,

The registration of the DestinationDataProvider should not happen in the application, the application should simply use the destionations via the JCoDestinationManager without having to know about the concrete DestinationDataProvider implementation. The registration should happen in a separate component. Separating destinations via namespace can be done, but is not necessary. It even might be bad if one wants to share connections between several applications.

Some comment on the concrete implementation of the DerstinationDataProvider: Creating a new instance of Properties in getDestinationProperties() is useless.

addDestinationByName() does not allow to overwrite an exsiting destination. How would one modify an existing one using this code?

Best regards,

Markus

Former Member
0 Kudos

Hi Markus,



1. The connection will not be shared among applications if we follow below approach:


e.g. there are two web applications deployed with name or conext:  W1 and W2. And SAP system name is S1


W1 add destination by name W1S1  and connects to the S1 using destination properties W1S1


addDestinationByName("W1"+"S1");


W2 add separate destiantion by name W2S1 and connects to S1 using destination properties W2S1


addDestinationByName("W2"+"S1");


This way we can maintain separate connection properties inside destination manager.



_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _



2. The existing destination can be modified:


By changing code of method  addDestinationByName(...) in MyJcoDestinationProvider class as:


public Boolean addDestinationByName(String destName, Properties destProperties) {

                

          //put method will always add new or overwrite(update) existing destination properties


          destinationMap.put(destName, destProperties);


         LOG.log(Level.INFO, "Destination " + destName + " properties added/updated successfully.");

          return true;

}


Similarly, the existing destination can be removed

By adding new method removeDestinationByName(...) method in MyJcoDestinationProvider class as:


public Boolean removeDestinationByName(String destName, Properties destProperties)

{

     if (destinationMap.containsKey(destName))

     {

          destinationMap.remove(destName, destProperties);


          LOG.log(Level.INFO, "Destination " + destName + ",remove operation successful.");


          return true;

     }

     else

     {

          LOG.log(Level.WARNING, "Destination "+destination+" properties not found, remove operation failed!");


          return false;

     }

}




Cheers,

Manoj Suppahiya

MarkusTolksdorf
Product and Topic Expert
Product and Topic Expert
0 Kudos

Hello Maoj,

you obviously have not understood my answer completely.

1. addDestinationByName() should not be part of an application - never. Application code should use destinations and not create some. This should be in separate components.

2. It's clear that you can separate connections by using namespaces for applications when configuring destinations. However, it does not make sense. An administrator wants to configure only once and destinations should be re-used across applications as well as connections.

3. Changing the destination works with your change, but this was not the major point.

Best regards,

Markus