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

com.sap.cloud.mt.runtime.DataSourceLookup Maven / Gradle / Ivy

There is a newer version: 3.3.3
Show newest version
/******************************************************************************
 * © 2020 SAP SE or an SAP affiliate company. All rights reserved.            *
 ******************************************************************************/

package com.sap.cloud.mt.runtime;

import com.sap.cloud.mt.subscription.DataSourceInfo;
import com.sap.cloud.mt.subscription.DbIdentifiers;
import com.sap.cloud.mt.subscription.InstanceLifecycleManager;
import com.sap.cloud.mt.subscription.SqlOperations;
import com.sap.cloud.mt.subscription.TenantMutexFactory;
import com.sap.cloud.mt.subscription.exceptions.InternalError;
import com.sap.cloud.mt.subscription.exceptions.UnknownTenant;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

import static com.sap.cloud.mt.runtime.DbUtils.getDbKey;

public abstract class DataSourceLookup {
    private static final Logger logger = LoggerFactory.getLogger(DataSourceLookup.class);
    private final ConcurrentHashMap tenantToDataSource = new ConcurrentHashMap<>();
    private final InstanceLifecycleManager instanceLifecycleManager;
    private final LibContainerCache libContainerCache = new LibContainerCache();
    private boolean oneDataSourcePerDb = false;


    protected DataSourceLookup(InstanceLifecycleManager instanceLifecycleManager, boolean oneDataSourcePerDb) {
        this.instanceLifecycleManager = instanceLifecycleManager;
        this.oneDataSourcePerDb = oneDataSourcePerDb;
    }

    protected DataSourceLookup(InstanceLifecycleManager instanceLifecycleManager) {
        this(instanceLifecycleManager, false);
    }

    DataSourceAndInfo getDataSourceAndInfo(String tenantId) throws InternalError, UnknownTenant {
        DataSourceAndInfo dataSourceAndInfo = tenantToDataSource.get(tenantId);
        if (dataSourceAndInfo != null) {
            return dataSourceAndInfo;
        } else {
            synchronized (TenantMutexFactory.get(tenantId)) {
                dataSourceAndInfo = tenantToDataSource.get(tenantId);
                if (dataSourceAndInfo != null) {
                    return dataSourceAndInfo;
                }
                logger.debug("Access instance manager for tenant {}", tenantId);
                DataSourceInfo tenantDataSourceInfo = instanceLifecycleManager.getDataSourceInfo(tenantId, false);
                String dbKey = getDbKey(tenantDataSourceInfo);
                DataSource dataSource = null;
                if (oneDataSourcePerDb) {
                    dataSource = libContainerCache.get(dbKey);
                    if (dataSource == null) {
                        preparePool(tenantDataSourceInfo);
                        dataSource = libContainerCache.get(dbKey);
                        if (dataSource == null) {
                            throw new InternalError("Could not find database pool for db key " + dbKey);
                        }
                    }
                } else {
                    //make sure that also in mode "one pool per tenant" lib containers are created
                    preparePool(tenantDataSourceInfo);
                    dataSource = create(tenantDataSourceInfo);
                }
                DataSourceAndInfo newEntry = new DataSourceAndInfo(dataSource, tenantDataSourceInfo);
                tenantToDataSource.put(tenantId, newEntry);
                return newEntry;
            }
        }
    }

    public void loadOneTenantPerDb() throws InternalError {
        Map> dbKeyToTenants = new HashMap<>();
        List tenantList = new ArrayList<>(instanceLifecycleManager.getAllTenants(false));
        tenantList.sort(Comparator.comparing(String::toString));
        tenantList.stream()
                .forEach(t -> {
                    try {
                        DataSourceInfo info = instanceLifecycleManager.getDataSourceInfo(t, false);
                        String dbKey = getDbKey(info);
                        List tenants = dbKeyToTenants.get(dbKey);
                        if (tenants == null) {
                            dbKeyToTenants.put(dbKey, new ArrayList<>());
                            tenants = dbKeyToTenants.get(dbKey);
                        }
                        tenants.add(t);
                    } catch (Exception e) {
                        //NOSONAR
                    }
                });
        dbKeyToTenants.entrySet().stream().forEach(e -> {
            for (String tenant : e.getValue()) {
                try {
                    getDataSourceAndInfo(tenant);
                    // Only one tenant per db
                    break;
                } catch (InternalError | UnknownTenant error) {
                    //try the next tenant for this db
                }
            }
        });
    }

    private synchronized void preparePool(DataSourceInfo dataSourceInfo) throws InternalError {
        logger.debug("Prepare pool for database key {}", getDbKey(dataSourceInfo));
        if (libContainerCache.isContained(getDbKey(dataSourceInfo))) {
            return;
        }
        List libContainers = instanceLifecycleManager.createAndGetLibContainers(dataSourceInfo);
        libContainers.stream().forEach(libContainerInfo -> {
            String dbKey = getDbKey(libContainerInfo);
            if (!libContainerCache.isContained(dbKey)) {
                try {
                    libContainerCache.insert(dbKey, create(libContainerInfo));
                } catch (InternalError internalError) {
                    logger.error("Cannot create data source pool for database key {}", dbKey);
                }
            }
        });
    }

    boolean fixDataSourceAfterCredentialChange(String tenantId, DataSource usedPool) throws InternalError {
        if (tenantId == null || tenantId.isEmpty()) {
            return false;
        }
        synchronized (TenantMutexFactory.get(tenantId)) {
            // Refresh only, if the container was really recreated for the same tenant
            // A parallel task could have fixed it already.
            // It must be avoided that pools that were fixed and are used are accidentally deleted
            DataSourceAndInfo dataSourceAndInfo = tenantToDataSource.get(tenantId);
            if (dataSourceAndInfo == null || usedPool != dataSourceAndInfo.getDataSource()) {
                //already removed from cache, or new pool created
                return true;
            }
            DataSourceInfo cachedInfo = dataSourceAndInfo.getDataSourceInfo();
            DataSourceInfo currentInfo;
            try {
                currentInfo = instanceLifecycleManager.getDataSourceInfo(tenantId, true);
            } catch (UnknownTenant unknownTenant) {
                currentInfo = null;
            }
            if (currentInfo == null) {
                adjustCacheAfterTenantDeletion(tenantId);
                return false;
            }
            if (credentialsChanged(cachedInfo, currentInfo)) {
                adjustCacheAfterCredentialChange(tenantId, dataSourceAndInfo, currentInfo);
                return true;
            } else {
                // credentials haven't changed => nothing to fix
                return false;
            }
        }
    }

    private void adjustCacheAfterCredentialChange(String tenantId, DataSourceAndInfo dataSourceAndInfo, DataSourceInfo currentInfo) {
        if (!oneDataSourcePerDb) {
            deleteFromCache(tenantId);
        } else {
            //credentials must be updated
            dataSourceAndInfo.setDataSourceInfo(currentInfo);
        }
    }

    private boolean credentialsChanged(DataSourceInfo cached, DataSourceInfo current) {
        return !StringUtils.equals(current.getPassword(), cached.getPassword()) ||
                !StringUtils.equals(current.getUser(), cached.getUser()) ||
                !StringUtils.equals(current.getSchema(), cached.getSchema()) ||
                !StringUtils.equals(current.getDbKey(), cached.getDbKey());
    }

    private void adjustCacheAfterTenantDeletion(String tenantId) {
        if (!oneDataSourcePerDb) {
            deleteFromCache(tenantId);
        } else {
            tenantToDataSource.remove(tenantId);
        }
    }

    void deleteFromCache(String tenantId) {
        if (tenantId == null) {
            return;
        }
        logger.debug("Instance for tenant {} deleted from cache", tenantId);
        synchronized (TenantMutexFactory.get(tenantId)) {
            DataSourceAndInfo dataSourceAndInfo = tenantToDataSource.get(tenantId);
            if (dataSourceAndInfo == null) {
                return;
            }
            tenantToDataSource.remove(tenantId);
            if (oneDataSourcePerDb) {
                long numberOfEntries = tenantToDataSource.entrySet().stream().filter(e ->
                        e.getValue().getDataSource() == dataSourceAndInfo.getDataSource()).count();
                if (numberOfEntries == 0) {
                    libContainerCache.remove(getDbKey(dataSourceAndInfo.getDataSourceInfo()));
                    closeDataSource(dataSourceAndInfo.getDataSource());
                }
            } else {
                closeDataSource(dataSourceAndInfo.getDataSource());
            }
        }
    }

    public void reset() {
        if (oneDataSourcePerDb) {
            tenantToDataSource.clear();
            libContainerCache.reset();
        } else {
            tenantToDataSource.entrySet().stream().forEach(e -> closeDataSource(e.getValue().getDataSource()));
            tenantToDataSource.clear();
        }
    }

    public List getCachedDataSource() {
        List result = new ArrayList<>();
        tenantToDataSource.entrySet().stream().forEach(e -> result.add(e.getValue().getDataSourceInfo().clone()));
        return result;
    }

    public List checkDataSourcePerDb(String dummySelectStatement) {
        List result = new ArrayList<>();
        libContainerCache.stream()
                .forEach(e -> {
                    try (Connection connection = e.getValue().getConnection()) {
                        SqlOperations sqlOperations = SqlOperations.build(instanceLifecycleManager.getDbType());
                        sqlOperations.setDummySelectStatement(dummySelectStatement);
                        sqlOperations.dummySelect(connection);
                        HealtCheckResult healtCheckResult = new HealtCheckResult(e.getKey(), true, null);
                        result.add(healtCheckResult);
                    } catch (InternalError | SQLException exception) {
                        HealtCheckResult healtCheckResult = new HealtCheckResult(e.getKey(), false, exception);
                        result.add(healtCheckResult);
                    }
                });
        return result;
    }

    public void checkDataSource(String tenantId, String dummySelectStatement) throws SQLException {
        DataSourceAndInfo dataSourceAndInfo = tenantToDataSource.get(tenantId);
        if (dataSourceAndInfo == null) {
            throw new SQLException("Tenant " + tenantId + " doesn't exist");
        } else {
            try (Connection connection = dataSourceAndInfo.getDataSource().getConnection()) {
                SqlOperations sqlOperations = SqlOperations.build(instanceLifecycleManager.getDbType());
                sqlOperations.setDummySelectStatement(dummySelectStatement);
                sqlOperations.dummySelect(connection);
            } catch (InternalError exception) {
                throw new SQLException(exception);
            }
        }
    }

    boolean doesTenantExist(String tenantId) throws InternalError {
        try {
            instanceLifecycleManager.checkThatTenantExists(tenantId);
            return true;
        } catch (UnknownTenant unknownTenant) {
            return false;
        }
    }

    protected abstract DataSource create(DataSourceInfo info) throws InternalError;

    protected abstract void closeDataSource(DataSource dataSource);

    public boolean isOneDataSourcePerDb() {
        return oneDataSourcePerDb;
    }

    public DbIdentifiers.DB getDbType() {
        return instanceLifecycleManager.getDbType();
    }

    public boolean hasDbIdentifiers() {
        return instanceLifecycleManager.hasDbIdentifiers();
    }


    private class LibContainerCache {
        private final ConcurrentHashMap dbKeyToLibContainer = new ConcurrentHashMap<>();

        private boolean isContained(String dbKey) {
            return dbKeyToLibContainer.containsKey(dbKey);
        }

        private void insert(String dbKey, DataSource dataSource) {
            dbKeyToLibContainer.put(dbKey, dataSource);
        }

        private DataSource get(String dbKey) {
            return dbKeyToLibContainer.get(dbKey);
        }

        private DataSource remove(String dbKey) {
            return dbKeyToLibContainer.remove(dbKey);
        }

        private void reset() {
            dbKeyToLibContainer.entrySet().stream().forEach(e -> closeDataSource(e.getValue()));
            dbKeyToLibContainer.clear();
        }

        private Stream> stream() {
            return dbKeyToLibContainer.entrySet().stream();
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy