cancel
Showing results for 
Search instead for 
Did you mean: 

JCO 3.0.6 on Tomcat server with multiple apps connecting to SAP

Former Member
0 Kudos

We have several Tomcat servers running SAP JCO 3.0.6 and are running into a problem when we attempt to load two web applications onto the Tomcat instance that both want to connect to SAP. What we are seeing is that Application1 has no problem communicating to SAP, but Application2 cannot register its destination data provider. Application2 gets the following error message:

java.lang.IllegalStateException: DestinationDataProvider already registered [com.Application1.types.sap.SAPDataProvider]
	at com.sap.conn.jco.rt.RuntimeEnvironment.setDestinationDataProvider(RuntimeEnvironment.java:133)
	at com.sap.conn.jco.ext.Environment.registerDestinationDataProvider(Environment.java:220)
...

Notice that the DestinationDataProvider already registered is the one for Application1!

This error implies that the code

Environment.registerDestinationDataProvider(myProvider);

applies to the JVM, and you cannot have multiple applications running in that JVM that each want to manage their own connections.

I can think of only two possible solutions to this with the JCO as it is currently coded:

1. Create a third application whose sole purpose is to manage SAP JCO destinations. Sounds ridiculous to me...

2. Have any application that wants to talk to SAP run in its own separate Tomcat instance. ...Equally absurd...

So, I have a few questions:

1. Has anyone else done this before, and if so, how?

2. Have I made a mistake in my analysis, and just not figured out how it should be done?

3. If I am correct, what will it take to get the JCO fixed so it can support multiple applications in the same Tomcat instance connecting to SAP?

Thank you,

Brett Hanson

Accepted Solutions (0)

Answers (2)

Answers (2)

Former Member
0 Kudos

I think the discussion in this [thread|; should help you resolve the issue. In your custom DestinationDataProvider you load the different properties and name them as different destinations. Using JCODestinationManager.getDestination( DestinationName ) you can direct your calls to the right server.

The reason for that, according to the documentation, is that the configuration of your destinations should not be coded but should be part of the infrastructure e.g. taken from an LDAP server, where you would load all necessary destinations. Even if you do not intend to do that you can just create one class MyDestinationDataProvider with all your properties and you can reuse them across your different applications.

Former Member
0 Kudos

I've already tried the suggestions in that link, and in fact, Application1 in my example already connects to two different SAP connections simultaneously. I've considered making the DataProvider the same across all the applications, but the problem that remains is that the DataProvider in Application1 doesn't know anything about the configuration properties in Application2. Lets say that Application1 knows about destinations "A" and "B". Application2 knows about destination "C". When Application2 attempts to use the DataProvider registered with Application1 to access destination "C", Application2 calls

JCoDestination destination = JCoDestinationManager.getDestination("C");

Application1's SAPDataProvider class calls method getDestinationProperties("C"), which will attempt to figure out what connection that actually means. It will go through its known connections and conclude that there is no connection with that name and throw

com.sap.conn.jco.JCoException: (106) JCO_ERROR_RESOURCE: Destination EHS_SERVER does not exist

I can't see any way right now that the SAPDataProvider in Application1 can know about the connection information for Application2.

I think it can also be argued that Application1 should not know about the configuration properties or any of the destination information of Application 2. Imagine for a minute that the Tomcat server is hosting two applications, one for Human Resources, and my application. With very little effort, I'd be able to figure out the name of the destination for the HR application. I'd then be able to retrieve the account information used to sign in, or just start my own connection to the HR connection and do what I want.

Brett

Former Member
0 Kudos

Sorry, overlooked the different instances in your code. Well, I don't really know about your setup, how you handle credentials in your tomcat instance etc. But you could use a shared resource to access your destination if you leave the credentials out of it and add username and password later on using the interface JCoCustomDestination. However, as long as you do not have control about who else is using JCo on your Tomcat it might be safer to install another Tomcat and just use separate JVMs. Depending on how you handle the authentication you could avoid that. I hope this is better directed at your problem.

Edit: For all this to work you need to have control over the properties of all destinations. You could create yourself a custom DataDestinationProvider which loads its settings from a file a database or any other source, package that in a jar and add it to both projects. Whatever app comes up first, loads the DataDestinationProvider, the second one just needs to check if it is loaded. I am not sure if that is the most elegant solution, but it should work.

Edited by: Christian Wunderlich on Jul 6, 2011 3:49 PM

Former Member
0 Kudos

Thanks Christian,

I have talked it over with the rest of the developers on my team and have decided to proceed with creating a jar file that will be added to all projects that talk to SAP. This code will register itself as the data provider if one hasn't been registered yet and add all destination information pertinent to the application to the data provider. It seems more logical to keep the connection information with the application rather than in a central location like a database.

Thanks again,

Brett Hanson

0 Kudos

Hello Brett,

your applications should be agnostic to the gory details as to how to connect to the SAP backend.

You should consider separating your DestinationProvider implementation into another component. Think of it like a connection pool you deploy into tomcat. This way you configure your connections as infrastructure and not as part of your application.

Your application should only call into the DestinationProvider with a destination name.

Regards

Dirk

Former Member
0 Kudos

Have a look at below thread...and see if you can get an idea...

basically you are trying to re-register the connection.....where it throws an error...

So either put it in if condition...or solve it as suggested in above thread....

Former Member
0 Kudos

Perhaps I wasn't clear enough. Application1 already has code very similar to that shown in the thread (see below), and it manages connections to two different SAP connections. Application1 has all the information it needs to manage connecting to the SAP instances it cares about. The problem arises when Application2 attempts to register its data provider. Application2's data provider contains the specifics on how to create a connection for Application2. Application1 doesn't have these specifics and cannot be responsible for creating the connection for other applications. Application2 is further unable to unregister Application1's data provider so it can register its own.

Here's Application1's code. First, There is a startup servlet that registers the data provider:

public void init(ServletConfig inConfig) throws ServletException {
	super.init(inConfig);

// Initialize connection pools to SAP.
	try {
		// Initialize the connection to SAP
		String dest = config.get("opis.sap.useConnection");
		this.logger.info("Initializing OPIS connection to SAP for " + dest);

		provider = new SAPDataProvider(dest);

		Environment.registerDestinationDataProvider(provider);
		this.logger.info("Registered connection to SAP - " + dest);
	} catch (IllegalStateException e) {
		this.logger.error("Failed to register connection to SAP: ", e);
	}
}

public void destroy() {
	try {
		Environment.unregisterDestinationDataProvider(provider);
		this.logger.info("Unregistered connection to SAP");
	} catch (IllegalStateException e) {
		this.logger.info("Failed to unregister connection to SAP: ", e);
	}
}

Edited by: Brett Hanson on Jul 5, 2011 11:45 AM

Edited by: Brett Hanson on Jul 5, 2011 11:50 AM - Added destroy method to post.

Former Member
0 Kudos

Sorry, problems displaying content, so I've broken my reply into separate parts...

Here is the code for the SAPDataProvider:

public class SAPDataProvider implements DestinationDataProvider {
	private Logger logger = LoggerFactory.getLogger(this.getClass());

	private String destinationName;
	private Properties connectionProperties;
	private DestinationDataEventListener listener;

	/**
	 * Creates the parameters required for connecting to SAP. Uses the opis.properties file to get
	 * the parameters.
	 * 
	 * @param inConnection
	 *            The line in the opis.properties file indicating which SAP connection to use.
	 * 
	 * 
	 */
	public SAPDataProvider(final String inConnection) {
		this.setProperties(inConnection);
	}

	public Properties getDestinationProperties(String inDestinationName) {
		if (StringUtils.isEmpty(inDestinationName)) {
			return null;
		}
		if (inDestinationName.equalsIgnoreCase(this.destinationName)) {
			return this.connectionProperties;
		}

		this.setProperties(inDestinationName);
		this.listener.updated(this.destinationName);

		return this.connectionProperties;
	}

	public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {
		this.listener = eventListener;
	}

	public boolean supportsEvents() {
		return true;
	}

	/**
	 * @param inConnection
	 *            the connection to set
	 */
	public void setConnection(String inConnection) {
		this.destinationName = inConnection;
	}

	/**
	 * @return the connection
	 */
	public String getConnection() {
		return this.destinationName;
	}

	private void setProperties(final String inDestinationName) {
		try {
			Configuration config = Configuration.getInstance();
			String host = config.get("opis.sap." + inDestinationName + ".ashost");
			String sysNumber = config.get("opis.sap." + inDestinationName + ".sysnr");
			String client = config.get("opis.sap." + inDestinationName + ".client");
			String userID = config.get("opis.sap." + inDestinationName + ".user");
			String password = config.get("opis.sap." + inDestinationName + ".passwd");
			String language = config.get("opis.sap.langu");
			String poolCapacity = config.get("opis.sap.poolCapacity");
			String peakLimit = config.get("opis.sap.peakLimit");

			this.connectionProperties = new Properties();
			this.connectionProperties.setProperty(DestinationDataProvider.JCO_ASHOST, host);
			this.connectionProperties.setProperty(DestinationDataProvider.JCO_SYSNR, sysNumber);
			this.connectionProperties.setProperty(DestinationDataProvider.JCO_CLIENT, client);
			this.connectionProperties.setProperty(DestinationDataProvider.JCO_USER, userID);
			this.connectionProperties.setProperty(DestinationDataProvider.JCO_PASSWD, password);
			this.connectionProperties.setProperty(DestinationDataProvider.JCO_LANG, language);

			// Connection Pooling information
			this.connectionProperties.setProperty(DestinationDataProvider.JCO_POOL_CAPACITY, poolCapacity);
			this.connectionProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT, peakLimit);

			this.destinationName = inDestinationName;
		} catch (FileNotFoundException e) {
			this.logger.error("Could not find configuration", e);
		} catch (IOException e) {
			this.logger.error("Could not read configuration", e);
		}
	}
}

The code in Application2 uses a different SAPDataProvider that looks at a different properties file to get its connection properties (see next post).

Edited by: Brett Hanson on Jul 5, 2011 11:47 AM

Former Member
0 Kudos

Again, sorry, the preview for the post worked, but the actual result was mangled. This is part 3 of 3.

public class SAPDataProvider implements DestinationDataProvider {
	private Logger logger = Logger.getLogger(this.getClass());

	private String destinationName;
	private Properties connectionProperties;
	private DestinationDataEventListener listener;

	static String EHS_SERVER = "EHS_SERVER";
	private String internalName = EHSConfiguration.getInstance().get("com.agrium.ehs.sapConnection");
	
	/**
	 * Creates the parameters required for connecting to SAP. Uses the EHS.properties file to get
	 * the parameters.
	 * 
	 * @param inConnection
	 *            The line in the EHS.properties file indicating which SAP connection to use.
	 * 
	 * 
	 */
	public SAPDataProvider(final String inConnection) {
		this.setProperties(inConnection);
	}

	public Properties getDestinationProperties(String inDestinationName) {
		this.logger.debug("SAP is requesting information on " + inDestinationName);
		if ((null == inDestinationName) || (inDestinationName.length() == 0)) {
			return null;
		}
		
		if (inDestinationName.equalsIgnoreCase(this.destinationName)) {
			this.logger.debug("The destination " + inDestinationName + " is already known, returning it - " + this.connectionProperties);
			return this.connectionProperties;
		}

		this.logger.debug("The destination " + inDestinationName + " was not known, setting properties.");
		this.setProperties(inDestinationName);
		this.listener.updated(this.destinationName);

		
		this.logger.debug("Properties updated.  Returning " + this.connectionProperties);
		return this.connectionProperties;
	}

	public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {
		this.listener = eventListener;
	}

	public boolean supportsEvents() {
		return true;
	}

	/**
	 * @param inConnection
	 *            the connection to set
	 */
	public void setConnection(String inConnection) {
		this.destinationName = inConnection;
	}

	/**
	 * @return the connection
	 */
	public String getConnection() {
		return this.destinationName;
	}

	private void setProperties(final String inDestinationName) {
		try {
			// retrieve connection details from config file
			JFig config = (JFig) JFig.getInstance(new JFigLocator("scheduler.config.xml"));
			String _client = config.getValue(this.internalName, "SAPCLIENT");
			String _user = config.getValue(this.internalName, "SAPUSER");
			String _pass = config.getValue(this.internalName, "SAPPASS");
			String _lang = config.getValue(this.internalName, "SAPLANG");
			String _host = config.getValue(this.internalName, "SAPHOST");
			String _sys = config.getValue(this.internalName, "SAPSYS");
	
			// set connection properties
			this.connectionProperties = new Properties();
			this.connectionProperties.setProperty(DestinationDataProvider.JCO_ASHOST, _host);
			this.connectionProperties.setProperty(DestinationDataProvider.JCO_SYSNR, _sys);
			this.connectionProperties.setProperty(DestinationDataProvider.JCO_CLIENT, _client);
			this.connectionProperties.setProperty(DestinationDataProvider.JCO_USER, _user);
			this.connectionProperties.setProperty(DestinationDataProvider.JCO_PASSWD, _pass);
			this.connectionProperties.setProperty(DestinationDataProvider.JCO_LANG, _lang);

			this.destinationName = inDestinationName;
		} catch (JFigException e) {
			this.logger.error("Could not read configuration", e);
		}
	}

	/**
	 * @return The application internal name (dev, qa, prod) of the sap connection.
	 */
	public String getSapConnectionName() {
		return this.internalName;
	}
}

The question is, if the data provider is registered for Application1, is it possible for any other application in the JVM to establish a connection to SAP at the same time?

Brett