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

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

The newest version!
/*******************************************************************************
 *   © 2019-2024 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.MtxTools;
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;

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 {
		logger.debug("Determine data source information for tenant {}", tenantId);
		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 = tenantDataSourceInfo.getDbKey();
				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 = info.getDbKey();

						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
				}
			}
		});
	}

	public boolean isNotAuthenticationProblem(SQLException sqlException) {
		return !isAuthenticationProblem(sqlException);
	}

	public boolean isAuthenticationProblem(SQLException sqlException) {
		try {
			SqlOperations sqlOperations = SqlOperations.build(instanceLifecycleManager.getDbType());
			return sqlOperations.isAuthenticationProblem(determineSqlState(sqlException));
		} catch (InternalError e) {
			logger.error("Not supported DB", e);
			//Not supported DB, should never happen
			return false;
		}
	}

	protected String determineSqlState(Throwable exception) {
		if (exception instanceof SQLException sqlException) {
			String sqlState = sqlException.getSQLState();
			if (StringUtils.isNotBlank(sqlState)) {
				return sqlState;
			}
		}
		if (exception.getCause() != null) {
			return determineSqlState(exception.getCause());
		}
		return "";
	}

	private synchronized void preparePool(DataSourceInfo dataSourceInfo) throws InternalError {
		logger.debug("Prepare pool for database key {}", dataSourceInfo.getDbKey());
		if (libContainerCache.isContained(dataSourceInfo.getDbKey())) {
			return;
		}
		List libContainers = instanceLifecycleManager.createAndGetLibContainers(dataSourceInfo);
		libContainers.stream().forEach(this::createAndCachePoolsForLibContainerPools);
	}

	private void createAndCachePoolsForLibContainerPools(DataSourceInfo libContainerInfo) {
		libContainerCache.createIfNotExist(libContainerInfo.getDbKey(), () -> create(libContainerInfo));
	}

	void fixDataSourceAfterCredentialChange(String tenantId, DataSource usedPool) throws InternalError {
		if (StringUtils.isBlank(tenantId)) {
			logger.debug("No tenant specified");
			throw new InternalError("No tenant specified");
		}
		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;
			}
			DataSourceInfo cachedInfo = dataSourceAndInfo.getDataSourceInfo();
			DataSourceInfo freshInfo;
			try {
				freshInfo = instanceLifecycleManager.getDataSourceInfo(tenantId, true);
			} catch (UnknownTenant unknownTenant) {
				freshInfo = null;
			}
			if (freshInfo == null) {
				adjustCacheAfterTenantDeletion(tenantId);
				logger.debug("Tenant was deleted");
				throw new InternalError("Tenant was deleted");
			}
			if (credentialsChanged(cachedInfo, freshInfo)) {
				adjustCacheAfterCredentialChange(tenantId, dataSourceAndInfo, freshInfo);
			} else {
				// credentials haven't changed => nothing to fix
				logger.debug("Normal database error");
				throw new InternalError("Normal database error");
			}
		}
	}

	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(dataSourceAndInfo.getDataSourceInfo().getDbKey());
					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(new DataSourceInfo(e.getValue().getDataSourceInfo())));
		return result;
	}

	public List checkDataSourcePerDb(String dummySelectStatement) {
		List result = new ArrayList<>();
		try {
			instanceLifecycleManager.getLibContainers().stream().forEach(this::createAndCachePoolsForLibContainerPools);
		} catch (InternalError e) {
			logger.error("Could not access containers owned by the mt-lib", e);
			HealtCheckResult healtCheckResult = new HealtCheckResult("", false, e);
			result.add(healtCheckResult);
			return result;
		}
		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();
	}

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

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

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

		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();
		}

		private void createIfNotExist(String dbKey, MtxTools.SupplierWithInternalError dataSourceSupplier) {
			try {
				dbKeyToLibContainer.putIfAbsent(dbKey, dataSourceSupplier.get());
			} catch (InternalError e) {
				logger.error("Cannot create data source pool", e);
			}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy