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

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

There is a newer version: 4.11.1
Show newest version
/*
 * (C) Copyright IBM Corp. 2020, 2021
 *
 * 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.sql.DataSource;
import javax.transaction.TransactionSynchronizationRegistry;

import com.ibm.fhir.config.FHIRConfigHelper;
import com.ibm.fhir.config.FHIRConfiguration;
import com.ibm.fhir.config.FHIRRequestContext;
import com.ibm.fhir.config.PropertyGroup;
import com.ibm.fhir.database.utils.model.DbType;
import com.ibm.fhir.persistence.exception.FHIRPersistenceException;
import com.ibm.fhir.persistence.jdbc.exception.FHIRPersistenceDataAccessException;
import com.ibm.fhir.persistence.jdbc.postgresql.SetPostgresOptimizerOptions;

/**
 * Common base for multi-tenant connection strategy implementations
 */
public abstract class FHIRDbConnectionStrategyBase implements FHIRDbConnectionStrategy, QueryHints {
    private static final Logger log = Logger.getLogger(FHIRDbConnectionStrategyBase.class.getName());
    // We use the sync registry to remember connections we've configured in the current transaction.
    private final TransactionSynchronizationRegistry trxSyncRegistry;

    // the action chain to be applied to new connections
    private final Action newConnectionAction;

    // Type and capability
    private final FHIRDbFlavor flavor;

    /**
     * Protected constructor
     * @param trxSyncRegistry the transaction sync registry
     * @param newConnectionAction actions to apply when a connection is created
     */
    protected FHIRDbConnectionStrategyBase(TransactionSynchronizationRegistry trxSyncRegistry, Action newConnectionAction) throws FHIRPersistenceDataAccessException {
        this.trxSyncRegistry = trxSyncRegistry;
        this.newConnectionAction = newConnectionAction;

        // initialize the flavor from the configuration
        this.flavor = createFlavor();
    }

    /**
     * Check with the transaction sync registry to see if this is the first time
     * we've worked with this connection in the current transaction.
     * @param connection the new connection
     * @param tenantId the tenant to which the connection belongs
     * @param dsId the datasource in the tenant to which the connection belongs
     */
    protected void configure(Connection connection, String tenantId, String dsId) throws FHIRPersistenceException {
        // We prefix the  key with the name of this class to avoid any potential conflict with other
        // users of the sync registry.
        final String key = this.getClass().getName() + "/" + tenantId + "/" + dsId;
        if (trxSyncRegistry.getResource(key) == null) {
            if (log.isLoggable(Level.FINE)) {
                log.fine("Configuring new connection in this transaction. Key='" + key + "'");
            }

            // first time...so we need to apply actions. Will be cleared when the transaction commits
            newConnectionAction.performOn(this.flavor, connection);

            // and register the key so we don't do this again
            trxSyncRegistry.putResource(key, new Object());
        } else {
            if (log.isLoggable(Level.FINE)) {
                log.fine("Connection already configured. Key='" + key + "'");
            }
        }
    }

    /**
     * Identify the flavor of the database using information from the
     * datasource configuration.
     * @return
     * @throws FHIRPersistenceException
     */
    private FHIRDbFlavor createFlavor() throws FHIRPersistenceDataAccessException {
        FHIRDbFlavor result;

        String datastoreId = FHIRRequestContext.get().getDataStoreId();

        // Retrieve the property group pertaining to the specified datastore.
        // Find and set the tenantKey for the request, otherwise subsequent pulls from the pool
        // miss the tenantKey.
        String dsPropertyName = FHIRConfiguration.PROPERTY_DATASOURCES + "/" + datastoreId;
        PropertyGroup dsPG = FHIRConfigHelper.getPropertyGroup(dsPropertyName);
        if (dsPG != null) {

            try {
                boolean multitenant = false;
                String typeValue = dsPG.getStringProperty("type");

                DbType type = DbType.from(typeValue);
                if (type == DbType.DB2) {
                    // We make this absolute for now. May change in the future if we
                    // support a single-tenant schema in DB2.
                    multitenant = true;
                }

                result = new FHIRDbFlavorImpl(type, multitenant);
            } catch (Exception x) {
                log.log(Level.SEVERE, "No type property found for datastore '" + datastoreId + "'", x);
                throw new FHIRPersistenceDataAccessException("Datastore configuration issue. Details in server logs");
            }
        } else {
            log.log(Level.SEVERE, "Missing datastore configuration for '" + datastoreId + "'");
            throw new FHIRPersistenceDataAccessException("Datastore configuration issue. Details in server logs");
        }

        return result;
    }

    /**
     * Get a connection configured for the given tenant and datasourceId
     * @param datasource
     * @param tenantId
     * @param dsId
     * @return
     */
    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();

        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) {
                // NOP...something bad is going on anyway, so don't confuse things
                // by throwing a different exception and hiding the original
            } finally {
                // just to prevent future coding mistakes
                connection = null;
            }
            throw t;
        }

        return connection;
    }

    @Override
    public FHIRDbFlavor getFlavor() throws FHIRPersistenceDataAccessException {
        return this.flavor;
    }

    @Override
    public String getHintValue(String hintProperty) {
        String result;
        String datastoreId = FHIRRequestContext.get().getDataStoreId();

        // Retrieve the property group pertaining to the specified datastore.
        // Find and set the tenantKey for the request, otherwise subsequent pulls from the pool
        // miss the tenantKey.
        String hintPropertyName = FHIRConfiguration.PROPERTY_DATASOURCES + "/" + datastoreId + "/hints";
        PropertyGroup hintPG = FHIRConfigHelper.getPropertyGroup(hintPropertyName);
        if (hintPG != null) {
            try {
                result = hintPG.getStringProperty(hintProperty, null);
            } catch (Exception x) {
                log.log(Level.WARNING, "getting property '" + hintProperty + "' for datastoreId: '" + datastoreId + "'");
                result = null;
            }

        } else {
            result = null;
        }

        return result;
    }

    @Override
    public QueryHints getQueryHints() {
        return this;
    }

    @Override
    public void applySearchOptimizerOptions(Connection c) {
        String datastoreId = FHIRRequestContext.get().getDataStoreId();

        switch (this.flavor.getType()) {
        case POSTGRESQL:
            // PostgreSQL needs optimizer options set to address search performance issues
            // as described in issue 1911
            final String pgName = FHIRConfiguration.PROPERTY_DATASOURCES + "/" + datastoreId + "/" + FHIRConfiguration.PROPERTY_JDBC_SEARCH_OPTIMIZER_OPTIONS;
            PropertyGroup optPG = FHIRConfigHelper.getPropertyGroup(pgName);
            SetPostgresOptimizerOptions cmd = new SetPostgresOptimizerOptions(optPG); // null optPG is OK
            cmd.applyTo(c);
            break;
        default:
            // NOP
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy