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:

/assets/jdbc/DataSourceAdd.png

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:

/assets/jdbc/firstRuntimeStep.png

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:

/assets/jdbc/firstRuntimeStep.png

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:

/assets/jdbc/DataSourceAdd.createDataSourceService.png

From the above screenshot, we can see an instance of LocalDataSourceService is created.

Here is the screenshot of the code running process:

/assets/jdbc/DataSourceService.png

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:

/assets/jdbc/DataSourceServices.png

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:

/assets/jdbc/WildFlyDataSource.png

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:

/assets/jdbc/org.jboss.as.connector.subsystems.datasources.AbstractDataSourceService.start(StartContext).png

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:

/assets/jdbc/AS7DataSourceDeployer.deploy(ServiceContainer).png

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:

/assets/jdbc/breakpoint-ds1.png

The above screenshot will start to register the datasource. Now let’s continue the server startup process, until it met the second breakpoint:

/assets/jdbc/breakpoint-ds2.png

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:

/assets/jdbc/breakpoint-ds3.png

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.