All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ibm.fhir.persistence.jdbc.connection.FHIRDbProxyDatasourceConnectionStrategy Maven / Gradle / Ivy

There is a newer version: 4.11.1
Show newest version
/*
 * (C) Copyright IBM Corp. 2020
 *
 * SPDX-License-Identifier: Apache-2.0
 */

package com.ibm.fhir.persistence.jdbc.connection;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.naming.InitialContext;
import javax.sql.DataSource;
import javax.transaction.TransactionSynchronizationRegistry;

import com.ibm.fhir.config.FHIRConfiguration;
import com.ibm.fhir.config.FHIRRequestContext;
import com.ibm.fhir.exception.FHIRException;
import com.ibm.fhir.persistence.exception.FHIRPersistenceException;
import com.ibm.fhir.persistence.jdbc.dao.impl.FHIRDbDAOImpl;
import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDBConnectException;

/**
 * Hides the logic behind obtaining a JDBC {@link Connection} from the DAO code.
 *
 * This strategy is used for configurations using the FHIR proxy datasource,
 * which supports dynamic configurations of datasources without requiring
 * the application server to restart.
 *
 * @implNote Refactored from {@link FHIRDbDAOImpl}. Improves separation of
 *           concerns by removing connection management code from the DAO
 *           and injecting it as a strategy instead. This not only simplifies
 *           things, but also makes it easier to implement new strategies,
 *           such as using a JEE datasource directly instead of the FHIR
 *           proxy datasource used here.
 */
@Deprecated
public class FHIRDbProxyDatasourceConnectionStrategy extends FHIRDbConnectionStrategyBase {
    private static final Logger log = Logger.getLogger(FHIRDbProxyDatasourceConnectionStrategy.class.getName());
    private static final String CLASSNAME = FHIRDbDAOImpl.class.getName();

    // number of nanoseconds in a millisecond
    private static final double NANOMS = 1e6;

    // The (proxy) datasource
    private final DataSource datasource;

    // JNDI address of the (proxy) datasource
    private final String datasourceJndiName;
    /**
     * Public constructor. The proxy datasource must be present (registered in JNDI)
     * at server startup.
     * @throws FHIRPersistenceDBConnectException if the proxy datasource is not configured
     */
    public FHIRDbProxyDatasourceConnectionStrategy(TransactionSynchronizationRegistry trxSyncRegistry, Action newConnectionAction) throws FHIRException {
        super(trxSyncRegistry, newConnectionAction);
        final String METHODNAME = "FHIRDbProxyDatasourceConnectionProvider()";
        // Find the JNDI name of the datasource we want to use
        try {
            this.datasourceJndiName =
                    FHIRConfiguration.getInstance().loadConfiguration().getStringProperty(
                        FHIRConfiguration.PROPERTY_JDBC_DATASOURCE_JNDINAME, FHIRDbConstants.FHIRDB_JNDI_NAME_DEFAULT);

            if (log.isLoggable(Level.FINE)) {
                log.fine("Using datasource JNDI name: " + datasourceJndiName);
            }
        } catch (Throwable e) {
            FHIRException fx = new FHIRPersistenceDBConnectException("Failure acquiring datasource");
            log.log(Level.SEVERE, fx.addProbeId("Failure to find proxy datasource in FHIR server configuration"), e);
            throw fx;
        }

        // JNDI lookup. May fail if the server configuration is incorrect
        try {
            InitialContext ctxt = new InitialContext();
            datasource = (DataSource) ctxt.lookup(datasourceJndiName);
        } catch (Throwable e) {
            FHIRException fx = new FHIRPersistenceDBConnectException("Failure acquiring datasource");
            log.log(Level.SEVERE, fx.addProbeId("Failure acquiring datasource: " + datasourceJndiName), e);
            throw fx;
        } finally {
            if (log.isLoggable(Level.FINEST)) {
                log.exiting(CLASSNAME, METHODNAME);
            }
        }
    }

    @Override
    public Connection getConnection() throws FHIRPersistenceDBConnectException {
        Connection connection;
        final String METHODNAME = "getConnection";

        if (log.isLoggable(Level.FINE)) {
            log.entering(CLASSNAME, METHODNAME);
        }

        try {
            // Resources can be routed to different databases using the dsId currently
            // set on the context.
            String tenantId = FHIRRequestContext.get().getTenantId();
            String dsId = FHIRRequestContext.get().getDataStoreId();

            long start = System.nanoTime();
            if (log.isLoggable(Level.FINE)) {
                log.fine("Getting connection for tenantId/dsId: [" + tenantId + "/" + dsId + "]...");
            }

            // Use the username/password interface to pass the tenantId and datasource id
            // paramters into the proxy datasource so that it can find the correct connection
            connection = getConnection(datasource, tenantId, dsId);

            if (log.isLoggable(Level.FINE)) {
                double deltams = (System.nanoTime() - start) / NANOMS;
                log.fine("Got the connection for [" + tenantId + "/" + dsId + "]. Took " + deltams + " ms");
            }
        } catch (Throwable e) {
            // Don't emit secrets in case they are returned to a client
            FHIRPersistenceDBConnectException fx =
                    new FHIRPersistenceDBConnectException("Failure acquiring connection for datasource");
            throw FHIRDbHelper.severe(log, fx, "Failure acquiring connection for datasource: " + datasourceJndiName, e);
        } finally {
            if (log.isLoggable(Level.FINE)) {
                log.exiting(CLASSNAME, METHODNAME);
            }
        }

        return connection;
    }

    @Override
    protected Connection getConnection(DataSource datasource, String tenantId, String dsId) throws SQLException, FHIRPersistenceException {
        // Now use the dsId/tenantId specific JEE datasource to get a connection
        Connection connection = datasource.getConnection(tenantId, dsId);

        try {
            // always
            connection.setAutoCommit(false);

            // configure the connection if it's the first time we've accessed it in this transaction
            configure(connection, tenantId, dsId);
        } catch (Throwable t) {
            // clean up if something goes wrong during configuration
            try {
                connection.close();
            } catch (Throwable x) {
                // Don't confuse things by propagating this exception...we want the original exception
                // to be the cause of failure. But log anyway, in case it's useful
                log.log(Level.FINEST, "close failed", x);
            } finally {
                // just to prevent future coding mistakes
                connection = null;
            }
            throw t;
        }

        return connection;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy