Adventures in JDBC and the PostgreSQL JDBC driver: Part 8 - The datasource loading process in Wildfly
Adventures in JDBC and the PostgreSQL JDBC driver: Part 8 - The datasource loading process in Wildfly
In previous article, I have introduced the JDBC driver loading process in Wildfly. In this article, I’d like to show you the datasource loading process.
Firstly, let’s review the datasource and driver configuration in standalone.xml
:
<subsystem xmlns="urn:jboss:domain:datasources:4.0">
<datasources>
<datasource jndi-name="java:jboss/datasources/weli" pool-name="weli" enabled="true" use-java-context="true">
<connection-url>jdbc:postgresql://localhost/weli</connection-url>
<driver>postgresql</driver>
<security>
<user-name>weli</user-name>
</security>
</datasource>
<drivers>
<driver name="postgresql" module="org.postgresql"/>
</drivers>
</datasources>
</subsystem>
The above XML data defines our PostgreSQL datasource and the driver. I have removed the sample H2 database configuration to focus on our PostgreSQL topic. In previous article, we have seen how does the postgresql
driver is loaded into Wildfly. The Wildfly server uses JdbcDriverAdd
class to do the driver loading work, and it will find the Driver
interface implementation for us automatically.
Now we should check how does Wildfly load the datasource
. From the above configuration, we can see the datasource
and driver
are actually two independent elements. In the datasource
configuration, it refers to the driver with the driver
item. Here is the snippet:
<driver>postgresql</driver>
So we can guess in the Wildfly code, the datasource will refer to the driver in such a loose way. Now let’s the Wildfly code side. The core class that deals with the datasource loading is AbstractDataSourceAdd
. It is an abstract class that contains most of the code, and DataSourceAdd
class extends it to add several addition methods. Here is the class diagram of these two classes:
From the above diagram, we can see the AbstractDataSourceAdd
class contains a performRuntime(...)
method. This is the entry point of the datasource handler, and we will check it in detail later. (Besides DataSourceAdd
class, there is another class called XaDataSourceAdd
that implements the AbstractDataSourcceAdd
class. We won’t discuss the two phase commit in this article, so we won’t check the XaDataSourceAdd
class.)
I set several breakpoints in the performRuntime(...)
method of the AbstractDataSourceAdd
class and start the Wildfly server in standalone mode. Here is the screenshot that the code stopped at breakpoint:
The above screenshot shows that the performRuntime(...)
method will call the firstRuntimeStep(...)
method.
I have set a breakpoint in firstRuntimeStep(...)
method, and here is the screenshot:
From the above screenshot, we can see in firstRuntimeStep(...)
method it will create an instance of AbstractDataSourceService
class by createDataSourceService(dsName, jndiName)
method. This is an abstract method that will be implemented by DataSourceAdd
class or the XaDataSourceAdd
class.
In our example, because we are using datasource
setting in configuration, and not the xa-datasource
, so we are actually using the DataSourceAdd.createDataSourceService(...)
method to create the an instance of LocalDataSourceService
. Here is the screenshot of the code in the method:
From the above screenshot, we can see an instance of LocalDataSourceService
is created.
Here is the screenshot of the code running process:
From the above diagram, we can see the instance of AbstractDataSourceService
is actually LocalDataSourceService
. Now let’s check the class diagram of these data source services:
The above diagram shows the AbstractDataSourceService
and its two implementation classes: LocalDataSourceService
and XaDataSourceService
. There is an important field in the AbstractDataSourceService
class, which is sqlDataSource : WildFlyDataSource
. The WildFlyDataSource
is the datasource type finally returned to the user. We will verify this later. Here is the class diagram of WildFlyDataSource
:
In this datasource class, the important methods are these getConnection(...)
methods. Users will get the database connections from the datasource, and don’t care whether these connections are pooled or not. Let’s see the code in WildFlyDataSource
:
public class WildFlyDataSource implements DataSource, Serializable {
/** DataSource */
private transient DataSource delegate;
public Connection getConnection() throws SQLException {
return delegate.getConnection();
}
...
From the above code, we can see WildFlyDataSource
will just use a delegate
datasource to do the real work. To see how does WildFlyDataSource
being created, we need to go back to DataSourceAdd.createDataSourceService(...)
method to see how does LocalDataSourceService
get created. Here is the code:
protected AbstractDataSourceService createDataSourceService(final String dsName,final String jndiName) throws OperationFailedException {
return new LocalDataSourceService(dsName, ContextNames.bindInfoFor(jndiName));
}
The above code shows that an instance of LocalDataSourceService
will be created, and its WildFlyDataSource
will be initialized during creation process. Let’s check the code of LocalDataSourceService
:
package org.jboss.as.connector.subsystems.datasources;
import org.jboss.as.naming.deployment.ContextNames;
import org.jboss.jca.common.api.validator.ValidateException;
import org.jboss.msc.inject.Injector;
import org.jboss.msc.value.InjectedValue;
/**
* Local data-source service implementation.
* @author John Bailey
* @author Stefano Maestri
*/
public class LocalDataSourceService extends AbstractDataSourceService {
private final InjectedValue<ModifiableDataSource> dataSourceConfig = new InjectedValue<ModifiableDataSource>();
public LocalDataSourceService(final String dsName, final ContextNames.BindInfo jndiName, final ClassLoader classLoader) {
super(dsName, jndiName, classLoader);
}
public LocalDataSourceService(final String dsName, final ContextNames.BindInfo jndiName) {
super(dsName, jndiName, null);
}
@Override
public AS7DataSourceDeployer getDeployer() throws ValidateException {
return new AS7DataSourceDeployer(dataSourceConfig.getValue().getUnModifiableInstance());
}
public Injector<ModifiableDataSource> getDataSourceConfigInjector() {
return dataSourceConfig;
}
}
From the class definition, we can see the LocalDataSourceService
class extends the AbstractDataSourceService
class, and the constructor of the LocalDataSourceService
class will just call the constructor AbstractDataSourceService
class.
We have seen the class diagram of AbstractDataSourceService
class in above, and we see the class stores the necessary attributes of a data source. The constructor of AbstractDataSourceService
code is listed in below:
protected AbstractDataSourceService(final String dsName, final ContextNames.BindInfo jndiName, final ClassLoader classLoader ) {
this.dsName = dsName;
this.classLoader = classLoader;
this.jndiName = jndiName;
}
From the above code, we can see the AbstractDataSourceService
class will store the name and JNDI name of a datasource. This info is extracted from the configuration file (standalone.xml
in standalone mode) set by the users.
Now we can check the start(...)
method of the AbstractDataSourceService
. Here is the sequence diagram the start(...)
method:
From the above diagram, we can see that the instance of WildFlyDataSource
is created in start(...)
method. To create the WildFlyDataSource
class instance, it uses the AS7DataSourceDeployer
to deploy a CommonDeployment
(the instance name is deploymentMD
) into the Wildfly container.
The AS7DataSourceDeploy
class is an inner class of AbstractDataSourceService
class, and its core part is the deploy(...)
method. Basicially speaking, the deploy(...)
method will register the datasource information and its driver reference into the container, and prepare the CommonDeployment
to fetch the WildFlyDataSource
from it. Here is the sequence diagram of the AbstractDataSourceService.deploy(...)
method:
From the above diagram, we can see that the datasource and its referred driver infomation will be wrapped into dataSourceConfig : org.jboss.jca.common.api.metadata.ds.DataSource
. This is the important thing we should know: Wildfly is using the JCA architecture(Java EE Connector Architecture) to wrap the datasource. In Wildfly, the JCA implementation is provided by the IronJacamar project(http://www.ironjacamar.org/). We can’t dive into too many details of this part in this article, and I’ll articles on the topic in future.
Now let’s go back to the AbstractDataSourceService.start(...)
method:
public synchronized void start(StartContext startContext) throws StartException {
try {
final ServiceContainer container = startContext.getController().getServiceContainer();
deploymentMD = getDeployer().deploy(container);
if (deploymentMD.getCfs().length != 1) {
throw ConnectorLogger.ROOT_LOGGER.cannotStartDs();
}
sqlDataSource = new WildFlyDataSource((javax.sql.DataSource) deploymentMD.getCfs()[0], jndiName.getAbsoluteJndiName());
...
From the above code, we can see that the WildFlyDataSource
is created from the org.jboss.jca.deployers.common.CommonDeployment
. In conclusion, our datasource and its relying driver information is registered into the connector system, and the datasource is managed by the connector system.
In addition, we can see the driver loading process and the datasource loading process are independent from each other. The driver loading is handled by JdbcDriverAdd
as we saw in last chapter, and the datasource loading is handled by DataSourceAdd
as we learned in this article.
During the DataSourceAdd
loading process, it will just record the JDBC driver needed by the Datasource, but it will not perform the action to connect the driver with datasource. We can verify this by setting multiple breakpoints in the code, and check when does the server will add the driver to the datasource. I have set some breakpoints in AbstractDataSourceAdd
and AbstractDataSourceService
and we will use them. In addition, let’s modify the datasource config in standalone.xml
a little bit:
<subsystem xmlns="urn:jboss:domain:datasources:4.0">
<datasources>
<datasource jndi-name="java:jboss/datasources/weli" pool-name="weli" enabled="true" use-java-context="true">
<connection-url>jdbc:postgresql://localhost/weli</connection-url>
<driver>void</driver>
<security>
<user-name>weli</user-name>
</security>
</datasource>
<drivers>
<driver name="postgresql" module="org.postgresql"/>
</drivers>
</datasources>
</subsystem>
From the above code, we can see our datasource refers to a driver named void
, and this driver is not configured anywhere. We can deduce that the Wildfly server will throw error for this config, because it can’t find the void
driver for the datasource. Now let’s run the Wildfly server in standalone mode to see what happened:
$ ./standalone.sh
...
20:40:27,768 INFO [org.jboss.as.connector.subsystems.datasources] (ServerService Thread Pool -- 33) WFLYJCA0005: Deploying non-JDBC-compliant driver class org.postgresql.Driver (version 42.0)
20:40:27,774 INFO [org.jboss.as.connector.deployers.jdbc] (MSC service thread 1-7) WFLYJCA0018: Started Driver service with driver-name = postgresql
...
20:40:30,919 ERROR [org.jboss.as.controller.management-operation] (Controller Boot Thread) WFLYCTL0013: Operation ("add") failed - address: ([
("subsystem" => "datasources"),
("data-source" => "weli")
]) - failure description: {
"WFLYCTL0412: Required services that are not installed:" => ["jboss.jdbc-driver.void"],
"WFLYCTL0180: Services with missing/unavailable dependencies" => [
"org.wildfly.data-source.weli is missing [jboss.jdbc-driver.void]",
"jboss.driver-demander.java:jboss/datasources/weli is missing [jboss.jdbc-driver.void]"
]
}
20:40:30,921 ERROR [org.jboss.as.controller.management-operation] (Controller Boot Thread) WFLYCTL0013: Operation ("add") failed - address: ([
("subsystem" => "datasources"),
("data-source" => "weli")
]) - failure description: {
"WFLYCTL0412: Required services that are not installed:" => [
"jboss.jdbc-driver.void",
"jboss.jdbc-driver.void"
],
"WFLYCTL0180: Services with missing/unavailable dependencies" => [
"org.wildfly.data-source.weli is missing [jboss.jdbc-driver.void]",
"jboss.driver-demander.java:jboss/datasources/weli is missing [jboss.jdbc-driver.void]",
"org.wildfly.data-source.weli is missing [jboss.jdbc-driver.void]"
]
}
20:40:31,155 INFO [org.jboss.as.controller] (Controller Boot Thread) WFLYCTL0183: Service status report
WFLYCTL0184: New missing/unsatisfied dependencies:
service jboss.jdbc-driver.void (missing) dependents: [service jboss.driver-demander.java:jboss/datasources/weli, service org.wildfly.data-source.weli]
I have extracted the relative log from server output. From the above output, we can see the datasources
subsystem is started, and the postgresql
driver is installed successfuly. During the datasource loading process, it reports error that it can not find the void
driver. This is what we expected.
Now let’s stop the server and restart it in debug mode to do the more fine-grained analysis. The first breakpoint that the startup process met is shown in below screenshot:
The above screenshot will start to register the datasource. Now let’s continue the server startup process, until it met the second breakpoint:
From the above screenshot, we can see the code stops at the last line of AbstractDataSourceAdd.firstRuntimeStep(...)
, and the last step is to add driver demander of the datasource. Until this point, the server still haven’t load the driver, and it just add the datasource with the connector. Then I continued the server startup process, and this time the code stopped at the breakpoint I set in JdbcDriverAdd
:
This is reasonable because in standalone.xml
we can see the driver
setting is in below of the datasource
setting.
<subsystem xmlns="urn:jboss:domain:datasources:4.0">
<datasources>
<datasource jndi-name="java:jboss/datasources/weli" pool-name="weli" enabled="true" use-java-context="true">
<connection-url>jdbc:postgresql://localhost/weli</connection-url>
<driver>void</driver>
<security>
<user-name>weli</user-name>
</security>
</datasource>
<drivers>
<driver name="postgresql" module="org.postgresql"/>
</drivers>
</datasources>
</subsystem>
From the above configuration, we can see that the the driver
section is belowing the datasource
section, but in datasource
section it refers to the driver with driver
item. By setting the breakpoints in AbstractDataSourceAdd
and JdbcDriverAdd
, we can also confirm the AbstractDataSourceAdd
executed before the JdbcDriverAdd
. In conclusion, the Wildfly datasource subsystem will connect the datasource with its driver after loading them independently.
Finally I continued the startup process, and the JdbcDriverAdd
successfully installed the jdbc driver. At last, the org.jboss.as.controller.management-operation
module threw the error as we see in above. We won’t dive into details of the management-operation
in this article.
In this article, I have introduced the datasource loading process. In next article, I’ll introduce the usage of datasource in Wildfly.