At Netage B.V. we are happy users of Stardog since version 4, as a Java shop we have plenty of options to connect and talk to the Stardog server which is great news. Our knowledge graph solutions are without exceptions based on a modular approach where various Java Web Archives (WAR) work together in a Java Container, in our case Tomee. All these applications need their own connection to the Stardog server, which leaves an interesting management challenge, how do you maintain the connection settings?
Sharing connection settings
When connecting to Stardog a set of connection parameters are required, at a minimum server, store, and credentials. The Stardog API by itself does not provide any way to abstract the injection of connection information, so it needs to be done programmatically for every connection needed, this leaves some options:
- Java Build time, use Maven or Gradle to inject the settings on compile-time, this has a big disadvantage that the generated code is not portable, every compiled unit only works on a specific connection
- Runtime .properties file to store all the information in properties and read them at runtime still need a build process to inject the properties file, but with overlays, it would be easy to have various builds for various connections.
- Java runtime properties, just provide the connection information in system properties or on the command line and figure it out at runtime. Although this works it is not directly a clean way of dealing with it.
- Use the container provided JNDI Resources to share a full connection string, this works and is relatively elegant, there is one central resource in the container that defines the connection parameters. And the connection string can be injected using the @Resource annotation, the Stardog API, however, expects the string in pieces to function properly
- Define a specific StardogConnection Resource as Java class to be defined in the Application Server, with specific properties needed for the connection, the application can have this resource injected and can access the various properties to connect to the server.
In this article, I’ll give some explanation of how this could be done together with code samples in a Github repo.
Lets start with a simple class to contain all our connection settings:
package nl.netage.stardogprovider;
public class StardogProvider {
protected String store, server, user, password;
public void setServer(String server) {
this.server = server;
}
public void setStore(String store) {
this.store = store;
}
public void setUser(String user) {
this.user = user;
}
public void setPassword(String password) {
this.password = password;
}
public String getServer() {
return server;
}
public String getStore() {
return store;
}
public String getUser() {
return user;
}
public String getPassword() {
return password;
}
}
As you can see this is a super straight forward class, to use this inside Tomee you need to compile it to a jar and copy it into you $TOMEE/lib
directory. You can then reference it in your $TOMEE/conf/tomee.xml
<Resource id="mystore" class-name="nl.netage.stardogprovider.StardogProvider">
server //localhost
store mystore
user guest
password readonly
</Resource>
Tomee will find the class for the resource and makes the resource available through JNDI to all client applications. In your client applications reference the store from your WEB-INF/web.xml
<resource-ref>
<res-ref-name>mystore</res-ref-name>
<res-type>nl.netage.stardogprovider.StardogProvider</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
Now in your application, you can use standard JNDI calls to get the information from the container and create a Stardog connection.
Context initCtx = new InitialContext();
StardogProvider provider = (StardogProvider) initCtx.lookup("java:comp/env/mystore");
Connection aConn = ConnectionConfiguration.to(provider.getStore())
.server(provider.getServer())
.credentials(provider.getUser(),provider.getPassword())
.connect();
Or with using @Resource injection:
@Resource(name = "mystore")
protected final StardogProvider provider = null;
if(provider != null)
Connection aConn = ConnectionConfiguration.to(provider.getStore())
.server(provider.getServer())
.credentials(provider.getUser(),provider.getPassword())
.connect();
So now we have created a method to configure Stardog Connection information at container level and use this information at application level without recompiling and redeploying the applications.
You only need to standardize internal store names used for you applications. This method allows you to stop the application container and switch out the server for a test server without touching any of your code!
The missing link…
Although with this solution you would think that the problem is solved, there is actually more to how Java Application Servers actually deal with connections to data servers.
Whenever a Java Application Server connects to SQL database it will use the DataSource resource type to not only specify the connection settings, but also a management interface around these connection, instead of directly getting a connection to a SQL database a connection pool is used in which the connections are maintained.
This pool has two advantages,
- A pool with a minimum set of available connections greatly improves performance, since the creation of connections can be time-consuming and resource-intensive when SSL connections are used. Whenever an application is done with a connection its returned to the pool, ready to be used by another application without initializing the connection first.
- You can limit the total amount of connections from the server to the data source, this way none of the applications can actually ‘run away’ and overload the data-source with connection requests when the pool gets exhausted this results in application-level errors.
Unfortunately, the DataSource resource type is not usable with Stardog, but wouldn’t it be great to be able to use the same technique for Stardog Connections? Luckily Stardog provides a Connection Pool which can be used to achieve exactly that, however, it has no implementation as Resource Adapter as used by Java Application Servers
Building a JEE Resource Adapter for Stardog
The rest of this article will be focusing on building a JEE Resource Adapter to be used in Tomee, again all the examples will be provided in a GitHub repository
To be able to use the connection pool we need to extend our earlier StardogConnection class with information about the pool size and allow it to emit connections
public class StardogConnectionProvider {
protected String store = null, server = null, user = null, password = null;
protected int minSize = 10, maxSize = 1000, block = 60, expire = 3600;
protected ConnectionPool standardPool = null, reasonPool = null;
public void setServer(String server) {
this.server = server;
}
public void setStore(String store) {
this.store = store;
}
public void setUser(String user) {
this.user = user;
}
public void setPassword(String password) {
this.password = password;
}
public void setMinSize(int minSize) {
this.minSize = minSize;
}
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
public void setBlock(int block) {
this.block = block;
}
public void setExpire(int expire) {
this.expire = expire;
}
public Connection getConnection() {
return getConnection(false);
}
public Connection getConnection(boolean reason) {
try {
if (reason) {
if (reasonPool == null) {
ConnectionConfiguration aConnConfig =
ConnectionConfiguration.to(store).server(server)
.credentials(user, password)
.reasoning(reason);
ConnectionPoolConfig aConfig =
ConnectionPoolConfig.using(aConnConfig)
.minPool(minSize)
.maxPool(maxSize)
.expiration(expire, TimeUnit.SECONDS)
.blockAtCapacity(block,TimeUnit.SECONDS);
reasonPool = aConfig.create();
}
return reasonPool.obtain();
} else {
if (standardPool == null) {
ConnectionConfiguration aConnConfig =
ConnectionConfiguration.to(store).server(server)
.credentials(user, password);
ConnectionPoolConfig aConfig =
ConnectionPoolConfig.using(aConnConfig)
.minPool(minSize)
.maxPool(maxSize)
.expiration(expire, TimeUnit.SECONDS)
.blockAtCapacity(block,TimeUnit.SECONDS);
standardPool = aConfig.create();
}
return standardPool.obtain();
}
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
return null;
}
}
We added a few parameters in the settings to reflect choices that are available in setting up Stardog connection pools, we also added a method to obtain a connection: getConnection()
and getConnection(boolean reason)
since the reasoning is property of a connection you see we need to create two pools to reflect this internally, so technically you’ll have twice the amount of connections reserved in your pool then what you specify. If you try to compile this code you’ll notice there are dependencies on the Stardog libraries for this code to work.
Here comes the tricky bit, since JEE containers use a different class loader for both the server itself and the applications, having the Stardog libraries in both your application as the containers $TOMEE/lib
directory will cause issues with these loaders (on top of that its a huge waste of memory to have multiple copies of the Stardog libs). To solve this problem you should move the libs inside the JEE containers library path and compile your programs with the dependencies as ‘provided’ this way only one set of libraries is used and the objects created by the resource adapters can be used in your application.
So how do we use this connection provider? It’s actually a lot simpler than in the previous example, you just ask for a connection from the provider:
Context initCtx = new InitialContext();
StardogConnectionProvider provider =
(StardogConnectionProvider) initCtx.lookup("java:comp/env/mystore" );
Connection aConn = provider.getConnection();
Or using @Resource injection:
Resource(name = "mystore")
protected final StardogConnectionProvider provider = null;
if(provider != null)
Connection aConn = provider.getConnection();
Now that we are using pools, it is extremely important that we close our connections when we are done with them! aConn.close()
so that the connection is returned to the pool, otherwise, you might run into pool exhausted exceptions!
Mind you the close() functionality has changed in Stardog 7.3.0 from the examples you can see we rely on this specific version ( or newer )
We have created a StardogConnectionProvider resource provider to be used in a Java EE container. We can centrally configure the settings and benefit from the connection pooling facilities to improve performance and prevent resource exhaustion. Mind you that the provided code is fairly rudimentary and could use a bit of cleanup and exception handling, the main purpose of the samples was to provide an example how this could be done.
But now that we have everything in place, how do I monitor the pool in my Container, how can I find resource-hogging applications?
Add monitoring to the provider
We have got everything set up to get and return connections from a centrally managed pool, but no way to monitor the actual state of that pool, luckily Java provides a standard way of dealing with that, JMX, Java Management Extension, essentially intended for this specific task.
So we are going to add an MBean to our implementation that allows us to show the status of our connection pool from a JMX console (e.g JConsole ), the elements we want to inspect are:
- server
- store
- user
- currently active connections
- maximum allowed connections
- currently idle connections
- minimum amount of connections in the pool
We need to create an MBean interface to be able to query these elements:
package nl.netage.stardog.connectionprovider;
public interface StardogPoolStatusMBean {
public String getstore();
public String getserver();
public String getuser();
public int getnumActive();
public int getmaxActive();
public int getnumIdle();
public int getminIdle();
}
And the corresponding class that is used in the backend:
public class StardogPoolStatus implements StardogPoolStatusMBean {
private GenericObjectPool<ConnectionConfiguration> internalPool=null;
private String server,store,user;
public StardogPoolStatus(String server, String store,String user ) {
this.server = server;
this.store = store;
this.user = user;
}
public void setPool(GenericObjectPool<ConnectionConfiguration> internalPool) {
this.internalPool = internalPool;
}
@Override
public int getnumActive() {
if(internalPool==null)
return 0;
return internalPool.getNumActive();
}
@Override
public String getstore() {
return store;
}
@Override
public String getserver() {
return server;
}
@Override
public int getmaxActive() {
if(internalPool==null)
return 0;
return internalPool.getMaxActive();
}
@Override
public int getnumIdle() {
if(internalPool==null)
return 0;
return internalPool.getNumIdle();
}
@Override
public int getminIdle() {
if(internalPool==null)
return 0;
return internalPool.getMinIdle();
}
@Override
public String getuser() {
return user;
}
}
The constructor accepts, the store, server, and user these are static elements that do not change, however, the GenericObjectPool is something that needs to be set at a later stage. We will need to extend the StardogConnectionProvider to set up the JMX environment once the connection provider is initialized.
public class StardogConnectionProvider {
protected String store = null, server = null, user = null, password = null;
protected int minSize = 10, maxSize = 1000, block = 60, expire = 3600;
protected boolean usePool = false;
protected ConnectionPool standardPool = null, reasonPool = null;
private StardogPoolStatus poolInformation = null, reasonPoolInformation = null;
public void setServer(String server) {
this.server = server;
setupWhenConfigured();
}
public void setStore(String store) {
this.store = store;
setupWhenConfigured();
}
public void setUser(String user) {
this.user = user;
setupWhenConfigured();
}
public void setPassword(String password) {
this.password = password;
setupWhenConfigured();
}
public void setMinSize(int minSize) {
this.minSize = minSize;
}
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
public void setBlock(int block) {
this.block = block;
}
public void setExpire(int expire) {
this.expire = expire;
}
public void setUsePool(boolean usePool) {
this.usePool = usePool;
}
public String getServer() {
return server;
}
public String getStore() {
return store;
}
public String getUser() {
return user;
}
public String getPassword() {
return password;
}
public Connection getConnection() {
return getConnection(false);
}
public Connection getConnection(boolean reason) {
try {
if (reason) {
if (reasonPool == null) {
ConnectionConfiguration aConnConfig =
ConnectionConfiguration.to(store)
.server(server)
.credentials(user, password)
.reasoning(reason);
ConnectionPoolConfig aConfig =
ConnectionPoolConfig.using(aConnConfig)
.minPool(minSize)
.maxPool(maxSize)
.expiration(expire,
TimeUnit.SECONDS)
.blockAtCapacity(block,
TimeUnit.SECONDS);
reasonPool = aConfig.create();
// Reflect our way to the internal pool.
Object mPool;
Field field = reasonPool.getClass()
.getDeclaredField("mPool");
field.setAccessible(true);
mPool = field.get(reasonPool);
Field field2 =
mPool.getClass()
.getDeclaredField("mPool");
field2.setAccessible(true);
reasonPoolInformation.setPool(
(GenericObjectPool<ConnectionConfiguration>)
field2.get(mPool));
}
return reasonPool.obtain();
} else {
if (standardPool == null) {
ConnectionConfiguration aConnConfig =
ConnectionConfiguration.to(store)
.server(server)
.credentials(user, password);
ConnectionPoolConfig aConfig =
ConnectionPoolConfig.using(aConnConfig)
.minPool(minSize)
.maxPool(maxSize)
.expiration(expire,
TimeUnit.SECONDS)
.blockAtCapacity(block,
TimeUnit.SECONDS);
standardPool = aConfig.create();
// Reflect our way to the internal pool.
Object mPool;
Field field =
standardPool.getClass()
.getDeclaredField("mPool");
field.setAccessible(true);
mPool = field.get(standardPool);
Field field2 =
mPool.getClass()
.getDeclaredField("mPool");
field2.setAccessible(true);
poolInformation.setPool(
(GenericObjectPool<ConnectionConfiguration>)
field2.get(mPool));
}
return standardPool.obtain();
}
} catch (IllegalArgumentException | IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
private void setupWhenConfigured() {
if (store != null && server != null && user != null && password != null)
{
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName beanName;
try {
beanName =
new ObjectName("Stardog:type=Connections,store="
+ this.store+",mode=NoReasoning");
this.poolInformation =
new StardogPoolStatus(server, store, user);
mbs.registerMBean(this.poolInformation, beanName);
beanName =
new ObjectName("Stardog:type=Connections,store="
+ this.store+",mode=Reasoning");
this.reasonPoolInformation =
new StardogPoolStatus(server, store, user);
mbs.registerMBean(this.reasonPoolInformation, beanName);
} catch (MalformedObjectNameException
| InstanceAlreadyExistsException
| MBeanRegistrationException
| NotCompliantMBeanException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
What has changed is that once all the static fields are set by the container an initial set up is executed through setupWhenConfigured() method, here we register the beans with the static information, one for reason and one for none reason connections, this way a JMX console can see the connections to the various stores with the details.
We also extended the pool creation on the getConnection() method, once the pool is created we link the Stardog pool to the mBean through the setPool() method. once this is done, the mBean methods to obtain pool status can access the status of the internal pool.
WARNING ‘HACK’
If you look closely at the code you’ll notice that we ‘reflect our way’ into the Stardog pool to get to the internal pool, we are using an internal non-public field two levels deep into the Stardog classes, this is considered a bad practice!
The end result
Now that we have implemented all facets of the resource connector we are able to define a Stardog pool at container level which is shared by all applications running on that container. Furthermore we have added a JMX Monitoring capability so that the actual status of the pool can be obtained using standard methods.
Below you see an example of the JConsole MBeans view for our connection.
Source code
All the source code in this article can be found in our : GitHub repository